bash script, get name of subdirectory - linux

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.

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

Silent while loop in bash

I am looking to create a bash script that keeps checking a file in directory and perform certain operation on it. I am using while loop, if file does not exists I want that while loop stays quite and keeps on checking condition. Here is what i created but it keeps throwing error that file not found, if file is not there.
while [ ! -f /home/master/applications/tmp/mydata.txt ]
do
cat mydata.txt;
rm mydata.txt;
sleep 1; done
There are two issue in your implementation:
You should use the same (absolute or relative) path in your while loop test statement [ ! -f $file ] and in your cat and rm commands.
The cat command is looking for the file in the current working directory (pwd) and your while statement might be looking somewhere else and hence, your implementation is buggy and won't work as expected if your pwd isn't /home/master/applications/tmp.
You need to move your cat command and rm command after the while block. It doesn't make sense to cat a file if a file doesn't exist. I think your misplaced those commands.
Try this:
file="/home/master/applications/tmp/mydata.txt"
while [ ! -f "$file" ]
do
sleep 1
done
cat $file
rm $file
EDIT
As per suggestion from #Ivan, you could use until instead of while as it suits more to your requirements.
file="/home/master/applications/tmp/mydata.txt"
until [ -f "$file" ]; do sleep 1; done
cat $file
rm $file
Making a different assumption than abhiarora, I'll guess maybe you meant for the file to reappear, and you want it shown each time.
file=/home/master/applications/tmp/mydata.txt
while :
do if [[ -f "$file" ]]
then echo "$(<"$file")"
rm "$file"
fi
sleep 1
done
This creates an infinite loop. If that's NOT what you wanted, use abhiarora's solution.

Remote to Local rolling backup script

I'm trying to create a bash script that runs through crontab to execute a backup remote to local. Everything works but my rolling backup part, where it only keeps 4 backups.
#!/bin/bash
dateForm=`date +%m-%d-%Y`
fileName=[redacted]-"$dateForm"
echo backup started for [redacted] on: $dateForm >> /home/backups/backLog.log
ls -tQ /home/backups/[redacted] | tail -n+5 | xargs -r rm
ssh root#[redacted] "tar jcf - -C /home/[redacted]/[redacted] ." > "/home/backups/[redacted]/$fileName".tar.bz2
if [ ! -f "/home/backups/[redacted]/$fileName.tar.bz2" ]
then
echo "something went wrong with the backup for $fileName!" >> /home/backups/backLog.log
else
echo "Backup completed for $fileName" >> /home/backups/backLog.log
fi
the ls line will work if executed in the directory just fine, but because crontab is executing it and I need the script to be outside of the folder it's targeting. I can't get it to target the rm to the correct directory utilizing the piped ls
I was able to come up with an interesting solution after studying the man page for ls a little more and utilizing find to grab the full paths.
ls -tQ $(find /home/backups/[redacted] -type f -name "*") | tail -n+5 | xargs -r rm
just posting an answer for someone that didn't want to create a rolling backup script that completely depended on date formatting, as there would ALWAYS be at least 4 backups in the folder targeted.

Check if rsync command ran successful

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

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