Check if rsync command ran successful - linux

The following bash-script is doing a rsync of a folder every hour:
#!/bin/bash
rsync -r -z -c /home/pi/queue root#server.mine.com:/home/foobar
rm -rf rm /home/pi/queue/*
echo "Done"
But I found out that my Pi disconnected from the internet, so the rsync failed. So it did the following command, deleting the folder.
How to determine if a rsync-command was successful, if it was, then it may remove the folder.

Usually, any Unix command shall return 0 if it ran successfully, and non-0 in other cases.
Look at man rsync for exit codes that may be relevant to your situation, but I'd do that this way :
#!/bin/bash
rsync -r -z -c /home/pi/queue root#server.mine.com:/home/foobar && rm -rf rm /home/pi/queue/* && echo "Done"
Which will rm and echo done only if everything went fine.
Other way to do it would be by using $? variable which is always the return code of the previous command :
#!/bin/bash
rsync -r -z -c /home/pi/queue root#server.mine.com:/home/foobar
if [ "$?" -eq "0" ]
then
rm -rf rm /home/pi/queue/*
echo "Done"
else
echo "Error while running rsync"
fi
see man rsync, section EXIT VALUES

Old question but I am surprised nobody has given the simple answer:
Use the --remove-source-files rsync option.
I think it is exactly what you need.
From the man page:
--remove-source-files sender removes synchronized files (non-dir)
Only files that rsync has fully successfully transferred are removed.
When unfamiliar with rsync it is easy to be confused about the --delete options and the --remove-source-files option. The --delete options remove files on the destination side. More info here:
https://superuser.com/questions/156664/what-are-the-differences-between-the-rsync-delete-options

you need to check the exit value of rsync
#!/bin/bash
rsync -r -z -c /home/pi/queue root#server.mine.com:/home/foobar
if [[ $? -gt 0 ]]
then
# take failure action here
else
rm -rf rm /home/pi/queue/*
echo "Done"
fi
Set of result codes here:
http://linux.die.net/man/1/rsync

Related

remove a subfolder with bash if it exsits

I have a subfolder of something like:
USERS = /opt/users/
I want to delete a subfolder of /opt/users without defining a new variable like:
if [ -d $USERS/phones ] && [-d $USERS/emails]; then
rm -Rf $USERS/phones
rm -RF $USERS/emails
else
echo "Folder does not exist, continuing the process"
fi
The question is can you suggest some smarter way or is good enough? I don't know how to handle if one of the folders is not existing or if I have another && condition? and the two commands rm -Rf I am not sure of the way it looks kinda ugly. Any recommendations?
First of all, some general bash tips:
Consider quoting strings
When to wrap quotes around a shell variable?
No spaces around =
Command not found error in Bash variable assignment
That said, lets take a closer look at rm -f:
-f, --force
ignore nonexistent files and arguments, never prompt
So there's no need to add extra checks if the folder exists.
You can change the code to:
USERS="/opt/users/"
rm -Rf "$USERS/phones"
rm -RF "$USERS/emails"
If you want to add a echo for each file that does not exist, we'll need to check if it exist:
How can I check if a directory exists in a Bash shell script?
Using a short if/else, the code could look something like this:
USERS="/opt/users/"
[[ -d "$USERS/phones" ]] && rm -Rf "$USERS/phones" || echo '/phones does not exists'
[[ -d "$USERS/emails" ]] && rm -Rf "$USERS/emails" || echo '/emails does not exists'
The above code can be simplified by using array's:
declare -a folders=("phones" "emails")
for i in "${arr[#]}"
do
[[ -d "$USERS/$i" ]] && rm -Rf "$USERS/$i" || echo "/$i does not exists"
done
A variation on OP's code assuming there could be a variable number of subdirectories to remove:
users=/opt/users/ # refrain from using ALL CAPS for user-defined variables
rmlist=(phones emails 'other schtuff') # array of subdirectory names
for subdir in "${rmlist[#]}"
do
[[ -d "${users}/${subdir}" ]] && rm -Rf "${users}/${subdir}" && continue
echo "Folder ${users}/${subdir} does not exist, continuing the process"
fi

bash script, get name of subdirectory

I am making a bash script to delete older timeshift snapshot, then create a new one, then update my system. I am stuck on how to pull the snapshot directory names from the timeshift directory.
#!/bin/bash
#We need to figure out which snap is older than the other without explicitly typing its name...
#Below, the names are explicitly typed!
#snaps=/timeshift/snapshots
snap1=/timeshift/snapshots/2020-12-29_16-43-41
snap2=/timeshift/snapshots/2021-01-01_09-59-12
if [ $snap1 -ot $snap2 ]
then
snap1=2020-12-29_16-43-41
sudo timeshift --delete --snapshot $snap1
if sudo timeshift --create --comments "Weekly update"; then
sudo pacman -Syu
else
echo "Something didn't work"
fi
fi
The snapshot names will not be static. How could I get this script to read the new name each time without me having to manually add it?
Here is the solution I went with after trying everything. I won't mark the other answer as I found the comment-answer (now deleted) more helpful. Thanks nonetheless.
#!/bin/bash
snaps=(/timeshift/snapshots/*)
if [[ ${snaps[0]} -ot ${snaps[1]} ]]
then
#$snaps becomes $snaps[0] here
sudo rm -fr $snaps && echo "$snaps has been deleted."
echo "Remaining snapshot:" && ls /timeshift/snapshots
if sudo timeshift --create --comments "weekly update"
then
sudo pacman -Syu
fi
else
echo "Something went wrong..."
fi
If there will ever only be exactly two files in /timeshift/snapshots and they are both folders created by timeshift and their names cannot contain whitespace of any kind, then you can remove the oldest one with this:
rm -fr `ls -tr /timeshift/snapshots | head -1`
If there is only one instead of two, then you have to be a bit more creative with the following, which will remove the older of the head and the tail so long as they aren't the same name. I assume you don't want to accidentally remove the only remaining backup before the next backup is taken.
cd /timeshift/snapshots
snap1=`ls -tr | head -1`
snap2=`ls -tr | tail -1`
if [ "$snap1" != "$snap2" ]; then
rm -fr "$snap1"
fi
The quotes allow for whitespace, if more robustness is required.

Having trouble moving files in bash

#! /bin/bash
for i in {0..9} ;
do
mkdir -p "d$i "
for j in {0..9};
do
if [ -e "./f$i$j.txt" ];
then
echo 'Moving!'
mv "./f$i$j.txt" "./d$i/f$j.txt"
fi
done
done
The above code is intended to search the current working directory for any files of name f##.txt where # is a number and arrange them into directories such that fAB.txt becomes dA/fB.txt. As far as I can tell it should work however I get the below error running the code.
Moving!
mv: cannot move './f48.txt' to './d4/f8.txt': No such file or directory
Try with this.
removed unnecessary ";"
#! /bin/bash
for i in {0..9}
do
mkdir -p "d$i"
for j in {0..9}
do
if [ -e "./f$i$j.txt" ]
then
echo 'Moving!'
mv "./f$i$j.txt" "./d$i/f$j.txt"
fi
done
done

Trouble with error handling in my first bash script

OK, so I am a total beginner with bash scripts and I am aware that the question is probably phrased a bit awkwardly, but I'll be as clear as I can!
I have written the following script to create a backup of repositories in a folder. The script is as follows:
#!/bin/bash
SVNREPO="/var/svn"
TEMP="/var/tmp"
BACKUP="/home/helix/backups"
cd $SVNREPO
if [ $# -eq 0 ]; then
for REPO in *; do
ARRAY+=($REPO)
done
else
for REPO in $#; do
ARRAY+=($REPO)
done
fi
for REPO in ${ARRAY[#]}; do
svnadmin dump $SVNREPO/$REPO -r HEAD | gzip > $TEMP/$REPO.svn.gzip sd
cp $TEMP/$REPO.svn.gzip $BACKUP/$REPO.svn.gzip
rm $TEMP/$REPO.svn.gzip
done
This script successfully produces .gzip backups of the all of the repositories in 'var/svn' when the script is called with no arguments, and creates .gzip backups of the specific repositories that are called as arguments. Great!
However, if the script is run with an argument that does not correspond to a repository that exists, then the program will crash with the error message: svnadmin: E000002: Can't open file '/var/svn/ada/format': No such file or directory. What I am trying to achieve is to catch this error and print a more user friendly output to the console. I have been trying to do this using 'trap'.
First I added the following line:
trap 'echo ERROR! The repository or repositories that you are trying to backup do not exist!' ERR
...and then I pushed the error to /dev/null at this point in the final for loop:
svnadmin dump $SVNREPO/$REPO -r HEAD 2>/dev/null | gzip > $TEMP/$REPO.svn.gzip
I pushed to the /dev/null file at the place I did because this is where the program errors out. However, the script no longer seems to work. What am I doing wrong here? Is it an issue to do with having the 2>/dev/null in the middle of a line? If so, how could I refactor this code so that it doesn't require the pipe in the middle of the line?
Many thanks for any help, I hope my question is reasonably clear! To confirm, the final non-working code is as follows:
#!/bin/bash
SVNREPO="/var/svn"
TEMP="/var/tmp"
BACKUP="/home/helix/backups"
cd $SVNREPO
if [ $# -eq 0 ]; then
for REPO in *; do
ARRAY+=($REPO)
done
else
for REPO in $#; do
ARRAY+=($REPO)
done
fi
trap 'echo ERROR! The repository or repositories that you are trying to backup do not exist!' ERR
for REPO in ${ARRAY[#]}; do
svnadmin dump $SVNREPO/$REPO -r HEAD 2>/dev/null | gzip > $TEMP/$REPO.svn.gzip sd
cp $TEMP/$REPO.svn.gzip $BACKUP/$REPO.svn.gzip
rm $TEMP/$REPO.svn.gzip
done
I do not know exactly how trap command works, but I will suggest another way that might solve your problem in another way:
First, before your for loop, add this line:
set -o pipefail
This means that when any command in a pipe fails, the last exit code ($?) will contain the error code if any failed.
On the line right after your svnadmin call, I would suggest adding this:
if [ $? -ne 0 ]; then
echo "ERROR! Received error code $? for repository '$REPO'."
continue
fi
You can of course alter the error message to your taste. The functionality should be clear: If svnadmin or bzip fails, it will print an error message and continue to next item in the for loop.
Hope this helps.
Using #cdarke suggestion of checking whether a file exists, I now have it working with the following code:
#!/bin/bash
SVNREPO="/var/svn"
TEMP="/var/tmp"
BACKUP="/home/helix/backups"
cd $SVNREPO
if [ $# -eq 0 ]; then
for REPO in *; do
ARRAY+=($REPO)
done
else
for REPO in $#; do
ARRAY+=($REPO)
done
fi
for REPO in ${ARRAY[#]}; do
if [ -f $SVNREPO/$REPO/format ]; then
vnadmin dump $SVNREPO/$REPO -r HEAD 2>/dev/null | gzip > $TEMP/$REPO.svn.gzip
cp $TEMP/$REPO.svn.gzip $BACKUP/$REPO.svn.gzip
rm $TEMP/$REPO.svn.gzip
else
echo ERROR! The repository $REPO does not exist. No backup has been made for this argument.
fi
done

prompt list of files before execution of rm

I started using "sudo rm -r" to delete files/directories. I even put it as an alias of rm.
I normally know what I am doing and I am quite experience linux user.
However, I would like that when I press the "ENTER", before the execution of rm, a list of files will show up on the screen and a prompt at the end to OK the deletion of files.
Options -i -I -v does not do what I want. I want only one prompt for all the printed files on screen.
Thank you.
##
# Double-check files to delete.
delcheck() {
printf 'Here are the %d files you said you wanted to delete:\n' "$#"
printf '"%s"\n' "$#"
read -p 'Do you want to delete them? [y/N] ' doit
case "$doit" in
[yY]) rm "$#";;
*) printf 'No files deleted\n';;
esac
}
This is a shell function that (when used properly) will do what you want. However, if you load the function in your current shell then try to use it with sudo, it won't do what you expect because sudo creates a separate shell. So you'd need to make this a shell script…
#!/bin/bash
… same code as above …
# All this script does is create the function and then execute it.
# It's lazy, but functions are nice.
delcheck "$#"
…then make sure sudo can access it. Put it in some place that is in the sudo execution PATH (Depending on sudo configuration.) Then if you really want to execute it precisely as sudo rm -r * you will still need to name the script rm, (which in my opinion is dangerous) and make sure its PATH is before /bin in your PATH. (Also dangerous). But there you go.
Here's a nice option
Alias rm to echo | xargs -p rm
The -p option means "interactive" - it will display the entire command (including any expanded file lists) and ask you to confirm
It will NOT ask about the recursively removed files. But it will expand rm * .o to:
rm -rf * .o
rm -rf program.cc program.cc~ program program.o backup?... # NO NO NO NO NO!
Which is much nicer than receiving the error
rm: .o file not found
Edit: corrected the solution based on chepner comment. My previous solutions had a bug :(
This simple script prompts for a y response before deleting the files specified.
rmc script file:
read -p "ok to delete? " ans
case $ans in
[yY]*) sudo rm "$#" ;;
*) echo "Nothing deleted";;
esac
Invoke thus
./rmc *.tmp
I created a script to do this. The solution is similar to #kojiro's.
Save the script with the filename del. Run the command sudo chmod a=r+w+x del to make the script an executable. In the directory in which you want to save the script, export the path by entering export PATH=$PATH:/path/to/the/del/executable in your '~/.bashrc' file and run source ~/.bashrc.
Here, the syntax of rm is preserved, except instead of typing rm ..., type del ... where del is the name of the bash script below.
#! /bin/bash
# Safely delete files
args=("$#") # store all arguments passed to shell
N=$# # number of arguments passed to shell
#echo $#
#echo $#
#echo ${args[#]:0}
echo "Files to delete:"
echo
n=`expr $N - 1`
for i in `seq 0 $n`
do
str=${args[i]}
if [ ${str:0:1} != "-" ]; then
echo $str
fi
done
echo
read -r -p "Delete these files? [y/n] " response
case $response in
[yY][eE][sS]|[yY])
rm ${args[#]:0}
esac

Resources