How to use rm interactively in a while read loop? - bash

I'm little stuck in a script that's used to find and delete certain files. I want to give the user the opportunity to iterate through the list and delete each file only after human approval.
However, I find the script to skip the user interaction and don't delete.
cat $fileToBeDeleted | while read in; do
rm -i "$in"
echo "deleted: $in"
done;

Instead of using rm -i you can use if statement to ask for confirmation before you delete the file. In this example I used a text file (to_delete_list.txt) that contains the list of files that I will be deleting.
read -u 1 answer will let you ask for user input in the loop.
#!/bin/bash
while IFS= read -r file; do
echo "do you want to delete $file (y/n)?"
read -u 1 answer
if [[ $answer = y ]]
then
rm $file
echo "deleted: $file"
else
continue
fi
done < "to_delete_list.txt"

Related

Check user input on "rm -i"?

Is there a way to check the user's response to a rm -i execution?
I'd like to echo something depending on whether or not the user responded with y or n.
The command returns successfully regardless of the user's response, so this attempt did not work:
$ rm -i testfile.txt && echo "The file was deleted."
remove testfile.txt? n
The file was deleted.
My reasoning was that the echo part would only be executed if the rm part was successful, but obviously a n response also counts as successful execution.
I would also like to be able to vary the message depending on the answer. This code would do it, but it's not very pretty.
file=testfile.txt
touch $file
read -p "Are you sure (y/n)? " answer
if [[ $answer =~ ^[yY](es|ES)?$ ]]; then
rm $file
echo "Deleted file."
else
echo "Did nothing."
fi
Surely there must be a way to get the input to rm -i.
How?
You can check, if the file was actually deleted. Eg.
rm -i testfile.txt && [[ ! -e testfile.txt ]] && echo "The file was deleted."
If the rm is successful, then test, if the file does not exist [[ ! -e file ]] and only then display the message. It covers the case, when you try to remove the file that does not exist, since then the rm will return with the exit code different than 0.
In case you want to display messages for when the file was purposefully deleted or not deleted, plus extra info on error, then you can extend the previous code like so:
rm -i testfile.txt && {
[[ ! -e testfile.txt ]] && echo "The file was deleted." || echo "Ignored"
} || echo "Error"
Most of the problem with your attempt is that you are trying to accommodate too many possible inputs, or paradoxically limiting the input to specifically "yes" or "no" while excluding "yeah", "yup", "nope", etc. A simply y or n will do (in fact, the only distinction you need to make is between y and not-y).
read -p "Are you sure (y/n)? " answer
if [[ $answer = [yY]* ]]; then
rm -- "$file"
echo "Deleted file."
else
echo "Did nothing."
fi
If you like, you can use shopt -s nocasematch before the if statement so that you can simply write if [[ $answer = y* ]]; then to ignore the case of the user's actual input.
Your original question would require some hook provided by rm itself, which it does not do.

Perform an undo action using a script

I have an excellent interactive script which sorts and processes a variety of filetypes from an unsorted folder into newly created directories.
I was wondering how I could write a small script or modify the existing script that so that I could unwind / undo the executed script and its sorting process back to its (pre sort) state if need be.
#!/bin/bash
read -p "Good Morning, Please enter your file type name for sorting [ENTER]:" all_extensions
if cd /Users/christopherdorman/desktop
then while read extension
do destination="folder$extension"
mkdir -p "$destination"
mv -v unsorted/*."$extension" "$destination"
done <<< "${all_extensions// /$'\n'}"
mkdir -p foldermisc
if mv -v unsorted/* "foldermisc"
then echo "Good News, the rest of Your files have been successfully processed"
fi
for i in folder*/; do
ls -S "$i" > "${i}filelist"
cat "${i}filelist" >> ~/desktop/summary.txt
done
fi
If you want to generate a script with an inverse action for each action you're performing, use printf %q to quote names in an eval-safe manner. For instance:
if [[ $undo_log ]]; then
# at the top of your script: open FD 3 as undo log
exec 3>"$undo_log"
fi
# later:
mv -v unsorted/*."$extension" "$destination"
# ...and, if we're generating an undo log, generate a sequence of appropriate commands
if [[ $undo_log ]]; then
for f in unsorted/*."$extension"
printf 'mv %q/%q %q\n' "$destination" "${f##*/}" "$f" >&3
done
fi

Having trouble with multiple variables using read in a while loop

Still learning and I'm getting lost with IFS=
#!/bin/bash
inotifywait -m -r --excludei '(.txt$|.old$|.swp$|.swx$|.swpx$|.zip$)' /path/to/watch -e create |
while read path action file; do
cd $path
USER="$(stat -c %U $file)"
echo "To: User <user#domain.com>
CC: Support <user#domain.com>
From: $USER <user#domain.com>
Subject: $USER has Uploaded a new File
The user '$USER' uploaded the file '$file' to '$path'" > /mnt/remote/ian/temp2.txt
cat /path/to/temp/file.txt | ssmtp list#domain.com
rm /path/to/temp/file.txt
done
This was my first ever script and it works great as long as there are no spaces in the the file names that get uploaded. I've read some scripts that use IFS= 'whatever' to define the field separators but I don't want to mess around with this while it's in production; it works, but it annoys me when I can't get the username of the user who uploaded the file. Please give me a hint.
This can be broken by an attacker who knows your implementation and wants to spoof arbitrary data (by creating filenames with newlines), but it's a rough first draft:
while IFS= read -r -d $'\n' path && IFS= read -r -d $'\n' file; do
user=$(stat -c %U "$file")
printf 'File %q created by %q in %q\n' "$file" "$user" "$path"
done < <(inotifywait --format $'%w\n%f\n' ~/tmp -r -e create)
I'd strongly suggest filing a ticket with upstream inotifytools requesting the ability use \0 in format strings.
Incidentally, this has already been brought up on the inotify-tools mailing list, where Stephane Chazelas offered a workaround:
nl="
"
inotifywait --syslog -e close_write -mr /tmp --format '%f///' |
while IFS= read -r file; do
while :; do
case $file in
(*///) file=${file%///}; break
esac
IFS= read -r extra || exit
file=$file$nl$extra
done
something with "$file"
done

Unix find a file and then prompting to delete [duplicate]

This question already has answers here:
How do I prompt for Yes/No/Cancel input in a Linux shell script?
(37 answers)
Closed 3 years ago.
I'm currently learning Unix and have come across a question in a book that I'm trying to solve.
I'm trying to write a script that asks a user to enter a file name.
Then, the script needs to check for file existence. If the file does not exist, that script should display an error message and then exit the script.
If the file exists, the script should ask if the user wants to delete the file:
If the answer is yes or y, the script should remove the file.
If the answer is no or n, the script should exit from the script.
If the answer is neither yes nor no, the script should display an error message and exit from the script.
This what I have written so far but have come across with a few errors:
#!/bin/bash
file=$1
if [ -f $file ];
then
echo read -p "File $file existes,do you want to delete y/n" delete
case $delete in
n)
exit
y) rm $file echo "file deleted";;
else
echo "fie $file does not exist"
exit
fi
If anyone come explain where I have gone wrong it would be greatly appreciated
I'd suggest this form:
#!/bin/bash
file=$1
if [[ -f $file ]]; then
read -p "File $file exists. Do you want to delete? [y/n] " delete
if [[ $delete == [yY] ]]; then ## Only delete the file if y or Y is pressed. Any other key would cancel it. It's safer this way.
rm "$file" && echo "File deleted." ## Only echo "File deleted." if it was actually deleted and no error has happened. rm probably would send its own error info if it fails.
fi
else
echo "File $file does not exist."
fi
Also you can add -n option to your prompt to just accept one key and no longer require the enter key:
read -n1 -p "File $file exists. Do you want to delete? [y/n] " delete
You added echo before read and I removed it.
In it's simplest form, you could do the following:
$ rm -vi file
To give you an example:
$ mkdir testdir; touch testdir/foo; cd testdir; ls
foo
$ rm -vi bar
rm: cannot remove 'bar': No such file or directory
$ rm -vi foo
rm: remove regular empty file 'foo'? y
removed 'foo'

Removing old directories with logs

My IM stores the logs according to the contact name. I have created a file with the list of active contacts. My problem is following:
I would like to create a bash script with read the active contacts names from the file and compare it with the directories. If the directory name wouldn't be found on the list, it would be moved to another directory (let's call it "archive"). I try to visualise it for you.
content of the list:
contact1
contact2
content of the dir
contact1
contact2
contact3
contact4
after running of the script, the content fo the dir:
contact1
contact2
contact3 ==> ../archive
contact4 ==> ../archive
You could use something like this:
mv $(ls | grep -v -x -F -f ../file.txt) ../archive
Where ../file.txt contains the names of the directories that should not be moved. It is assumed here that the current directory only contains directories, if that is not the case, ls should be replaced with something else. Note that the command fails if there are no directories that should be moved.
Since in the comments to the other answer you state that directories with whitespace in the name can occur, you could replace this by:
for i in *
do
echo $i | grep -v -x -q -F -f ../file.txt && mv "$i" ../archive
done
This is an improved version of marcog's answer. Note that the associative array requires Bash 4.
#!/bin/bash
sourcedir=/path/to/foo
destdir=/path/to/archive
contactfile=/path/to/list
declare -A contacts
while read -r contact
do
contacts[$contact]=1
done < "$contactfile"
for contact in "$sourcedir"/*
do
if [[ -f $contact ]]
then
index=${contact##*/}
if [[ ! ${contacts[$index]} ]]
then
mv "$contact" "$destdir"
fi
fi
done
Edit:
If you're moving directories instead of files, then change the for loop above to look like this:
for contact in "$sourcedir"/*/
do
index=${contact/%\/}
index=${index##*/}
if [[ ! ${contacts[$index]} ]]
then
mv "$contact" "$destdir"
fi
done
There might be a more concise solution, but this works. I'd strongly recommend prefixing the mv with echo to test it out first, otherwise you could end up with a serious mess if it doesn't do what you want.
declare -A contacts
for contact in "$#"
do
contacts[$contact]=1
done
ls a | while read contact
do
if [[ ! ${contacts[$contact]} ]]
then
mv "a/$contact" ../archive
fi
done

Resources