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
Related
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.
I am kind of new to bash scripting and know some of the basics. I need help creating a bash script that I can run that will search a server (or multiple servers) for a list of specific /home/users and then if it finds a /user from the list it deletes that user's directory using sudo rm -rf /user. If it doesn't find a listed user, it does nothing. this script should be adaptable to run on multiple servers that may or may not have any users on them. Any help would be appreciated.
I posted what I have been thinking. I would start by creating a file called userList containing all the home directories I want to delete, one per-line:
/Users/user1
/Users/user30
#!/usr/bin/bash
do
if [[ -d $dir ]]
then
rm -R $dir
echo “Directory $dir found and deleted.”
else
echo “Directory $dir not found.”
fi
done < userList
you're close:
#!/bin/bash
while read dir; do # <-- there's a change here
if [[ -d $dir ]]
then
sudo rm -rf $dir # <-- and here
echo “Directory $dir found and deleted.”
else
echo “Directory $dir not found.”
fi
done < userList
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
I am working on a bash script that I am working on for a universal Linux dotfile install script. I am attempting to get the symlinking working but I have been bashing (no pun intended) my head against the wall trying to figure out why the symlinks will not work and the copying will not work. I currently have this separated into multiple files so I don't have if statements three miles long.
ultimate-install.sh
#! /bin/bash
#
# The ultimate install script for all dotfiles.
if [[ -z "$1" ]]; then
echo "Please specify the directory where all of you dotfiles are located."
exit 1
fi
# Makes sure that the directory does NOT have a trailing slash!
if [[ ${1:(-1)} == "/" ]]; then
DOTFILE_DIR=${1:0:${#1} - 1}
else
DOTFILE_DIR="$1"
fi
# TODO: Clean this mess up and make it more concise.
if [[ -z "$2" ]]; then
if [[ ! -d $HOME/.config/old_dotfiles ]]; then
mkdir "$HOME/.config/old_dotfiles"
fi
BACKUP_DIR="$HOME/.config/old_dotfiles"
else
if [[ -d "$2" ]]; then
BACKUP_DIR="$2"
else
mkdir "$2"
BACKUP_DIR="$2"
fi
fi
read DECISION
if [ $DECISION == "N" -o $DECISION == "n" ]; then
echo "Aborting installation!"
exit
fi
read DECISION
echo
if [ $DECISION == "N" -o $DECISION == "n" ]; then
source src/no-prompts.sh "$DOTFILE_DIR" "$BACKUP_DIR"
else
source src/prompts.sh "$DOTFILE_DIR" "$BACKUP_DIR"
fi
echo "Installation complete. Old dotfiles are backed up to $BACKUP_DIR."
src/no-prompts.sh
#! /bin/bash
#
# Maintained by Daniel Seymour
DOTFILE_DIR="$1"
BACKUP_DIR="$2"
TEST_DIR="/home/daniel/dotfile-test"
function no_prompt_install(){
FILE_NAME="$1"
if [ "${FILE_NAME:0:1}" == "." ]; then
ln -s "$FILE_NAME $TEST_DIR/$FILE_NAME"
else
ln -s ".$FILE_NAME $TEST_DIR/$FILE_NAME"
fi
}
# TODO: implement a check for file type and deal with unknown files.
for FILE in $DOTFILE_DIR/*; do
cp $FILE $BACKUP_DIR
no_prompt_install $FILE
done
src/prompts.sh
#! /bin/bash
#
# Maintained by Daniel Seymour
DOTFILE_DIR="$1"
BACKUP_DIR="$2"
TEST_DIR="/home/daniel/dotfile-test"
function prompt_install {
FILE_PATH=$1
FILE_NAME=${FILE_PATH##*/}
echo "Would you like to install $FILE_NAME? [Y, n]"
read DECISION
if [ $DECISION == "n" -o $DECISION == "N" ]; then
echo "Not installing."
return
else
# TODO: Clean this up into one statement.
if [ ${FILE_NAME:0:1} == "." ]; then
rm -rf "$TEST_DIR/$FILE_NAME"
ln -sn "$FILE_PATH $TEST_DIR/$FILE_NAME"
else
FILE_NAME="."$FILE_NAME
rm -rf "$TEST_DIR/$FILE_NAME"
ln -sn "$FILE_PATH $TEST_DIR/$FILE_NAME"
fi
fi
}
# TODO: implement a check for file type and deal with unknown files.
for FILE in $DOTFILE_DIR/*; do
cp $FILE $BACKUP_DIR
prompt_install $FILE
done
The above is trimmed for long echo statements that do a lot of explaining.
The basic idea of this script is to take as many as two arguments (the dotfile directory to install and if specified, the custom backup directory, $1 and $2 respectively). The script should then copy all of the files in the target directory to BACKUP_DIR and symlink all of the dotfiles in the DOTFILE_DIR to TEST_DIR. (TEST_DIR will be $HOME in the production scripts.) Great in theory, right?
The complication comes when I run the script. None of the files are copied or symlinked as they should be. Instead, I end up with NO copy (probably due to the same issue as the symlink not working) and a broken symlink in the current directory.
One last piece of information. I am executing the file from the directory that contains ultimate-install.sh (/home/daniel/Projects/Git-Repos/Ultimate-Dotfile-Install-Scripts).
So where did I go wrong?
PS Please don't comment on the TODOs. :)
Short answer
Your quoting is wrong.
ln -sn -- "$FILE_PATH" "$TEST_DIR/$FILE_NAME"
Longer answer
This does not really relate to your problem, but I want to point it out.
Do not use "" inside [[ ]], so instead of this if [[ -z "$1" ]]; then use this if [[ -z $1 ]]; then
What is the point of making sure that directory does not have a trailing slash? It has no effect! /usr/bin/ is the same directory as /usr/bin or /usr////bin or /usr////////bin//////
Do not check if a directory exists when creating directories. Use -p option! Example: mkdir -p "$HOME/.config/old_dotfiles"
Instead of if [ $DECISION == "N" -o $DECISION == "n" ]; use if [[ ${DECISION^^} == N]];
I have another great answer about bash code style HERE. Please check it out! Also read the comments, since I was explaining there exactly your issue.
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