Linux shell script : make a folder with the current date name - linux

I am trying to make a simple backup script and i have problem creating a folder with the curent date for name
My script is that and basically the problem is on the last line
drivers=$(ls /media/)
declare -i c=0
for word in $drivers
do
echo "($c)$word"
c=c+1
done
read -n 1 drive
echo
c=0
for word in $drivers
do
if [ $c -eq $drive ]
then
backuppath="/media/$word/backup"
fi
c=c+1
done
echo "doing back up to $backuppath"
cp -r /home/stefanos/Programming $backuppath/$(date +%Y-%m-%d-%T)
Ouput:
(0)0362-BA96
(1)Data
(2)Windows
0
doing back up to /media/0362-BA96/backup
cp: cannot create directory `/media/0362-BA96/backup/2012-12-05-21:58:37': Invalid argument
The path is triply checked that is existing until /media/0362-BA96/
SOLVED:
Did what janisz said the final script looks like
drivers=$(ls /media/)
declare -i c=0
for word in $drivers
do
echo "($c)$word"
c=c+1
done
read -n 1 drive
echo
c=0
for word in $drivers
do
if [ $c -eq $drive ]
then
backuppath="/media/$word/backup"
fi
c=c+1
done
echo "doing back up to $backuppath"
backup(){
time_stamp=$(date +%Y_%m_%d_%H_%M_%S)
mkdir -p "${backuppath}/${time_stamp}$1"
cp -r "${1}" "${backuppath}/${time_stamp}$1"
echo "backup complete in $1"
}
#####################The paths to backup####################
backup "/home/stefanos/Programming"
backup "/home/stefanos/Android/Projects"
backup "/home/stefanos/Dropbox"

Trying changing it to:
time_stamp=$(date +%Y-%m-%d-%T)
mkdir -p "${backuppath}/${time_stamp}"
cp -r /home/stefanos/Programming "${backuppath}/${time_stamp}"

: is not valid on FAT (it is used to specify disk). Some of M$ invalid character works on GNU/Linux systems but it is safer to avoid them (just replace with .). Use following date format
date +%Y_%m_%d_%H_%M_%S
It should works on most file systems but it could be too long for MS DOS FAT. More info you will find here.

Related

Linux Shell Script * or ???? is replace automatically

I have some files stored on a server. I have to get all those files using a pattern and exclude the file which contains the current date as the file name.
Files are given below
/var/tomcat/logs/catalina.2022-05-11.log
/var/tomcat/logs/catalina.2022-05-13.log
/var/tomcat/logs/catalina.2022-05-14.log
/var/tomcat/logs/catalina.2022-05-16.log
/var/tomcat/logs/error_1.log
/var/tomcat/logs/error_2.log
/var/tomcat/logs/error_3.log
/var/tomcat/logs/error_4.log
For this, I have stored patterns in a file and I want to read the pattern of that file and find all files with the help of those patterns.
Pattern Input File content is given below, in below I have used '%Y-%m-%d' to identify the date format so that I can exclude the current date file.
/var/tomcat/logs/catalina.*.log;%Y-%m-%d
/var/tomcat/logs/error_*.log
I have developed a shell script which is given below
#!/bin/sh
pattern_filepath=$1
while IFS= read -r line || [ -n "$line" ]; do
pattern_var="$line"
echo pattern: "$pattern_var"
filepath=""
date_format=""
if [[ $pattern_var == *";"* ]];
then
echo "Semicolons ; separator is there"
filepath=($(echo "$pattern_var" | cut -f1 -d ';'))
echo filepath: "$filepath"
date_format=($(echo "$pattern_var" | cut -f2 -d ';'))
else
echo "Semicolons ; separator is not there"
filepath=$pattern_var
fi
echo "date_format: "$date_format
done < "$pattern_filepath"
Command to run the script
sh /var/monitoring/test.sh "/var/monitoring/pattern" > /var/monitoring/test.log
Inside the log file, I can see, that in the file path variable I am getting the value as a date but that should be with an asterisk instead of a date.
Log file 'test.log'
pattern: /var/tomcat/logs/catalina.*.log;%Y-%m-%d
Semicolons ; separator is there
filepath: /var/tomcat/logs/catalina.2022-05-11.log
date_format: %Y-%m-%d
pattern: /var/tomcat/logs/error_*.log
Semicolons ; separator is not there
date_format:
Please help me with this, how can I achieve this?
Simple, straight to the point. Achieves the requirement, without the complexity of patterns, or lists, or ...
#!/bin/bash
sourcedir="/var/tomcat/logs"
if [[ ! -d "$sourcedir" ]]
then
echo "ERROR: source directory $sourcedir does not exist."
exit 1
fi
targetdir="/tmp/somedir"
if [[ ! -d "$targetdir" ]]
then
echo "ERROR: target directory $targetdir does not exist."
exit 1
fi
# Get the catalina logs
cp "$sourcedir"/catalina.*.log "$targetdir"
# Remove the catalina log for today
filetoremove="$targetdir"/catalina.$(date +%Y-%m-%d).log
if [[ -f "$filetoremove" ]]
then
rm -f "$filetoremove"
fi
# Get the error logs
cp "$sourcedir"/error_*.log "$targetdir"
You can add error checking and recovery for the cp and rm commands.

Why cant I get mv to rename and copy file?

The last thing I want to do in this script is take the userinput to rename the corresponding files to include .bak extension rather then .txt which will copy into a backup directory. I keep receiving an error messaging saying e.g.
mv: cannot stat '5.bak': No such file or directory
The snippet in question (right at the bottom of full code):
for i in ~/students/Stu$userinput/*_$userinput.txt;
do
mkdir -p ~/students/Backup; mv -- "$i" "${userinput%.txt}.bak" ~/students/Backup; #Change extension.
done
Full code:
#!/bin/bash
echo "Current User: $USER" >> system_info.txt #Inputs current user in .txt file.
echo "Current Directory: $PWD" >> system_info.txt #Inputs current user directory in .txt file.
#Creating the directory structure in the users home directory.
for i in {1..20}; #Create sub-directory of Stu from 1 to 20.
do
mkdir -p archive students/Stu${i}; #Two new dirctories created, with Stu having sub directories represented by {i},
done
i=1 #{i} to begin at 1.
until ((i>20)) #Each loop to check if i is greater than 20.
do
touch ~/students/Stu${i}/Notes_$i.txt #Creates a Notes page for every i up to 20.
touch ~/students/Stu${i}/Results_$i.txt #Similarily, creates a txt file for Results_$ up untill 20.
((i++))
done
for i in {1..20}; do #This is to echo the required sentence howcasing the current filename, user in the corresponding directory.
filename=~/students/Stu${i}/Notes_$i.txt
touch "$filename"
echo "The $(basename -- "$filename") file belonging to $USER was created in the Stu${i}." >> ~/students/Stu${i}/Notes_$i.txt
done
for i in {1..20}; do #This is to echo the required sentence howcasing the current filename, user in the corresponding directory.
filename=~/students/Stu${i}/Notes_$i.txt
touch "$filename"
echo "The $(basename -- "$filename") file belonging to $USER was created in the Stu${i}." >> ~/students/Stu${i}/Notes_$i.txt
done
for i in {1..20}; do
file_name=~/students/Stu${i}/Results_$i.txt
touch "$file_name"
echo "The $(basename -- "$file_name") file belonging to $USER was created in the Stu${i}." >> ~/students/Stu${i}/Results_$i.txt
done
while true;
do
echo -n "File to change: "
read userinput #Sets variable
if [ "$userinput" -ge 1 ] && [ "$userinput" -le 20 ];then #If userinput is greater than or equal to 1 or lss than or equal to 20/
echo "Valid number! File changed." #If fits crities, let user kniw
break
else #If critriea doesnt meet, try again.
echo "Invalid! File number must be between 1-20. Please try again."
fi
done
for i in ~/students/Stu$userinput/*_$userinput.txt;
do
mkdir -p ~/students/Backup; mv -- "$i" "${userinput%.txt}.bak" ~/students/Backup; #Change extension.
done
echo $?
The correct one would be
cp $i ~/students/Backup/$(basename ${i%.txt}.bak);
Note: mkdir -p ~/students/Backup is not needed to put in the loop

Improve Bash script to obtain file names, remove part of their names, and display the result until a key is entered

I have fully working code that obtains a list of file names from the '$testDir', removes the final 3 characters from each, runs a different script using the adjusted names, and displays a list of the edited names that had errors when used in the seperate script, until the letter 'q' is entered by the user.
Here is the code:
#!/bin/bash
# Declarations
declare -a testNames
declare -a errorNames
declare -a errorDescription
declare resultDir='../Results'
declare outputCheck=''
declare userEntry=''
declare -i userSelect
# Obtain list of files in $resultDir and remove the last 3 chars from each file name
for test in `ls $resultDir`; do
testNames+=("${test::-3}")
done
# Run 'checkFile.sh' script for each adjusted file name and add name and result to apporopriate arrays if 'checkFile.sh' script fails
for f in "${testNames[#]}"; do
printf '%s' "$f: "
outputCheck=$(./scripts/checkFile.sh -v "${f}" check)
if [[ $outputCheck != "[0;32mNo errors found.[0m" ]];
then
errorNames+=("$f")
errorDescription+=("$outputCheck")
echo -e '\e[31mError(s) found\e[0m'
else
printf '%s\n' "$outputCheck"
fi
done
#Prompts user to save errors, if any are present
if [ "${#errorNames[#]}" != 0 ];
then
until [[ $userEntry = "q" ]]; do
echo "The following tests had errors:"
for(( i=1; i<=${#errorNames[#]}; i++ )); do
echo -e $i: "\e[31m${errorNames[i-1]}\e[0m"
done
echo "Enter the corresponding number in the list to save the errors found or enter 'q' to quit"
read -r userEntry
numInput=$userEntry
if [ $numInput -gt 0 -a $numInput -le ${#errorNames[#]} ];
then
mkdir -p ./Errors
echo -e "File" "\e[96m${errorNames[$userEntry-1]}""_Error_Info.txt\e[0m created in the 'Errors' folder which contains details of the error(s)"
echo "${errorDescription[$userEntry-1]}" > "./Errors/""${errorNames[$userEntry-1]}""_Error_Info.txt"
fi
done
echo 'Successfully Quit'
exit $?
fi
echo 'No errors found'
exit $?
As someone who is new to Bash and programming concepts, I would really appreciate any suggested improvements to this code. In particular, the script needs to run quickly as the 'checkFile.sh' script takes a long time to run itself. I have a feeling the way I have written the code is not concise either.

Export variables to another script

I am making 2 scripts. The first script is going to take a file, and then move it to a directory named "Trash". The second script will recover this file and send it back to it's original directory. So far I have the first script moving the file correctly.
Here is my code so far:
For delete.sh
FILE=$1
DATEDELETED=$(date)
DIRECTORY=$(pwd)
mv $1 Trash
echo $FILE
echo $DATEDELETED
echo $DIRECTORY
Output:
trashfile
Sun Mar 2 21:37:21 CST 2014
/home/user
For undelete.sh:
PATH=/home/user/Trash
for file in $PATH
do
$file | echo "deleted on" | date -r $file
done
echo "Enter the filename to undelete from the above list:"
EDIT: So I realized that I don't need variables, I can just list all the files in the Trash directory and edit the output to what I want. I'm having a little trouble with my for statement though, I'm getting these two errors: ./undelete.sh: line 6: date: command not found
./undelete.sh: line 6: /home/user/Trash: Is a directory. So I'm not exactly sure what I'm doing wrong in my for statement.
Here is the expected output:
file1 deleted on Tue Mar 16 17:15:34 CDT 2010
file2 deleted on Tue Mar 16 17:15:47 CDT 2010
Enter the filename to undelete from the above list:
Well I've accomplished what could be a workaround for what your scenario is trying to accomplish.
Basically you can enter echo "script2variable=$script1variable" >> script2.sh from script1.sh. Then use the source command to call that script later from any script you desire. Might have to just play with the theories involved.
Good Luck!
Delete Script file
#!/bin/bash
# delete.sh file
# Usage: ./delete.sh [filename]
#DATEDELETED=$(date) #not best solution for this kind of application
DIR=$(pwd)
fn=$1
#Specify your trash directory or partition
trash=~/trash
#Set path and filename for simple use in the script
trashed=$trash/$fn.tgz
#Send variables to new manifest script.
echo "#!/bin/bash" > $1.mf.sh
echo "DIR=$DIR" >> $1.mf.sh
# Questionable need?
#echo "DATEDELETED=$DATEDELETED" >> $1.mf.sh
echo "mv $1 $DIR" >> $1.mf.sh
echo Compressing
tar -cvpzf $trashed $1 $1.mf.sh
if [ $? -ne 0 ]; then
echo Compression Failed
else
echo completed trash compression successfully
echo Trashbin lists the file as $trashed
rm $1 -f
rm $1.mf.sh -f
echo file removed successfully
fi
exit 0
Restore Script File
#!/bin/bash
# restore.sh
# Usage: ./restore.sh
# filename not required for this script, use index selection
fn=$1
#SET LOCATION OF TRASH DIRECTORY
trash=~/trash
listfile=($(ls $trash))
#SET COUNTER FOR LISTING FILES = 0
i=0
#THIS IS JUST A HEADER FOR YOUR OUTPUT.
clear #cleanup your shell
echo -e INDEX '\t' Filename '\t' '\t' '\t' Date Removed
#Echo a list of files from the array with the counter.
for FILES in "${listfile[#]}"
do
echo -e $i '\t' $FILES '\t' "deleted on $(date -r $trash/$FILES)"
let "i += 1"
done
#Output total number of items from the ls directory.
echo -e '\n' $i file\(s\) found in the trash!
# 1 Offset for 1 = 0, 2 = 1, and so on.
let "i -= 1"
#Require input of a single, double, or triple digit number only.
#Loop back prompt if not a number.
while true;
do
read -p "Enter an index number for file restoration: " indx
case $indx in
[0-9]|[0-9][0-9]|[0-9][0-9][0-9] ) break ;;
* ) read -p "Please enter a valid number 0-$i: " indx ;;
esac
done
#
script=$(echo ${listfile[$indx]}|sed 's/\.tgz/\.mf.sh/g')
tar -xvpzf $trash/${listfile[$indx]}
rm $trash/${listfile[$indx]}
sleep 2
chmod +x $script
source $script
rm $script
Run the script with source
source <yourscript>
or
. ./<yourscript>
In your case
. ./delete.sh && ./undelete.sh
Hope this will help

Find all files where no part of the path of the file is a symbolic link

Is there an easy way to find all files where no part of the path of the file is a symbolic link?
Short:
find myRootDir -type f -print
This would answer the question.
Care to not add a slash at end of specified dir ( not myRootDir/ but myRootDir ).
This won't print other than real files in real path.
No symlinked file nor file in symlinked dir.
But...
If you wanna ensure that a specified dir contain a symlink, there is a litte bash function to could do the job:
isPurePath() {
if [ -d "$1" ];then
while [ ! -L "$1" ] && [ ${#1} -gt 0 ] ;do
set -- "${1%/*}"
if [ "${1%/*}" == "$1" ] ;then
[ ! -L "$1" ] && return
set -- ''
fi
done
fi
false
}
if isPurePath /usr/share/texmf/dvips/xcolor ;then echo yes; else echo no;fi
yes
if isPurePath /usr/share/texmf/doc/pgf ;then echo yes; else echo no;fi
no
So you could Find all files where no part of the path of the file is a symbolic link in running this command:
isPurePath myRootDir && find myRootDir -type f -print
So if something is printed, there are no symlink part !
You can use this script : (copy/paste the whole code in a shell)
cat<<'EOF'>sympath
#!/bin/bash
cur="$1"
while [[ $cur ]]; do
cur="${cur%/*}"
if test -L "$cur"; then
echo >&2 "$cur is a symbolic link"
exit 1
fi
done
EOF
${cur%/*} is a bash parameter expansion
EXAMPLE
chmod +x sympath
./sympath /tmp/foo/bar/base
/tmp/foo/bar is a symbolic link
I don't know any easy way, but here's an answer that fully answers your question, using two methods (that are, in fact, essentially the same):
Using an auxiliary script
Create a file called hasnosymlinkinname (or choose a better name --- I've always sucked at choosing names):
#!/bin/bash
name=$1
if [[ "$1" = /* ]]; then
name="$(pwd)/$1"
else
name=$1
fi
IFS=/ read -r -a namearray <<< "$name"
for ((i=0;i<${#namearray[#]}; ++i)); do
IFS=/ read name <<< "${namearray[*]:0:i+1}"
[[ -L "$name" ]] && exit 1
done
exit 0
Then chmod +x hasnosymlinkinname. Then use with find:
find /path/where/stuff/is -exec ./hasnosymlinkinname {} \; -print
The scripts works like this: using IFS trickery, we decompose the filename into each part of the path (separated by the /) and put each part in an array namearray. Then, we loop through the (cumulative) parts of the array (joined with the / thanks to some IFS trickery) and if this part is a symlink (see the -L test), we exit with a non-success return code (1), otherwise, we exit with a success return code (0).
Then find runs this script to all files in /path/where/stuff/is. If the script exits with a success return code, the name of the file is printed out (but instead of -print you could do whatever else you like).
Using a one(!)-liner (if you have a large screen) to impress your grand-mother (or your dog)
find /path/where/stuff/is -exec bash -c 'if [[ "$0" = /* ]]; then name=$0; else name="$(pwd)/$0"; fi; IFS=/ read -r -a namearray <<< "$name"; for ((i=0;i<${#namearray[#]}; ++i)); do IFS=/ read name <<< "${namearray[*]:0:i+1}"; [[ -L "$name" ]] && exit 1; done; exit 0' {} \; -print
Note
This method is 100% safe regarding spaces or funny symbols that could appear in file names. I don't know how you'll use the output of this command, but please make sure that you'll use a good method that will also be safe regarding spaces and funny symbols that could appear in a file name, i.e., don't parse its output with another script unless you use -print0 or similar smart thing.

Resources