Having trouble with multiple variables using read in a while loop - bash

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

Related

How to use rm interactively in a while read loop?

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"

Cannot pass variable into while loop

I've tried this a few ways but trying to pass a variable into this code when it's a text file just doesn't work. What's weird though is if the check sees it's just a url, it works perfectly.
I've tried -i in wget, quotes around $line, {} around line, putting $directory a few ways into wget. Nothing. It either reads it as blank or as the file name, not the urls in the file.
On top of this mess, $savefile.log in the first part of the loop always returns directory.txt.log. Tried $line.log to fix that and nada. I do need it stripped as : and \ are not valid in a file name.
#!/bin/bash
read -p "Enter directory or .txt file: `echo $'\n> '`" directory
savefile=$(echo "${directory//"http://"}" | cut -d '/' -f1)
if [[ $directory == *.txt ]]
echo
echo "Spidering $directory"
while IFS='' read -r line || [[ -n "$line" ]]; do
echo "$line"
wget -np --spider -e robots=off --no-check-certificate $line 2>> $savefile.log
echo
echo "Spider saved to $savefile.log"
done < $directory
else
echo
echo "Spidering $directory"
wget -r -np --spider -e robots=off --no-check-certificate $directory 2>> $savefile.log
echo
echo "Spider saved to $savefile.log"
fi
EDITx2??:
Removing my old answer because it was wrong, finally got a chance to sit down and run the code, and based off what I THINK you were looking for this should do it:
#!/bin/bash
read -p "Enter directory or .txt file: `echo $'\n> '`" directory
if [[ $directory == *.txt ]]; then
echo
echo "Spidering $directory"
while IFS='' read -r line || [[ -n "$line" ]]; do
echo "$line"
savefile="$(basename "${directory//.txt}")"
wget -np --spider -e robots=off --no-check-certificate $line 2>> $savefile.log
echo
echo "Spider saved to $savefile.log"
done < $directory
else
echo
echo "Spidering $directory"
savefile=$(echo "${directory//"http://"}" | cut -d '/' -f1)
wget -r -np --spider -e robots=off --no-check-certificate $directory 2>> $savefile.log
echo
echo "Spider saved to $savefile.log"
fi
One big issue was you forgot the ; then at the end of the if [[ ... ]] line.
With savefile, I am guessing you were aiming to strip off extra after the ".com" on a url and give just the file name of a file? Works perfect for a url as written, but it nukes file path. Moved it into the else as written. For a file, basename removes the directories and just leaves the file name and the variable expansion strips the troublesome '.txt'.
Tried not to deviate from your code by much, but I would recommend quoting the variables - shouldn't be spaces in the URL, but ~could be~ if input wrong, but mainly because filenames could have un-escaped spaces.
Could also make a tad more compact by using 'echo -e "\nSpidering $directory"' instead of the double echoes. Don't suppose anything wrong with them but they bug my ocd. :P
Finally, I would recommend using the newer syntax for the command substitution, "$(echo "cmd")" instead of the backticks. Again not technically wrong, but since the back-ticks are deprecated they may eventually stop working. Also, and more so I'd say, it makes the code allot more readable, I have to squint at times to tell if it is a single quote or a back-tick.

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

Bash Read Input - Tab the Prompt

I've got a script that I'm reading the input from users. Here's my code:
if [ -z $volreadexists ]; then
echo -e "\tThis will overwrite the entire volume (/dev/vg01/$myhost)...are you sure?"
read REPLY
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo -e "\t\tContinuing"
syncvolume
else
echo "Fine...skipping"
fi
fi
I'm having to use read REPLY because read by itself doesn't insert tabs. What I'm looking for is something similar to:
read -p "\tDoes this look OK? (n for No)" -n 1 -r
Where \t would tab over the read prompt.
How can I add tabs to a read prompt?
UPDATE: Thanks for the great answer from #gniourf!:
read -p $'\tDoes this look OK? (n for No)' -n 1 -r
However, I found an issue. When I attempt to use a variable there it doesn't translate it:
read -p $'\tThis will overwrite the entire volume (/dev/vg01/$myhost)...are you sure? ' -n 1 -r
becomes
This will overwrite the entire volume (/dev/vg01/$myhost)...are you sure?
where I want:
This will overwrite the entire volume (/dev/vg01/server1)...are you sure?
Using doublequotes doesn't work either :(
Any ideas?
Just use ANSI-C quoting:
read -p $'\tDoes this look OK? (n for No)' -n 1 -r
Now, if you want to use variable expansions too, you can mix different quotes like so:
read -p $'\t'"This will overwrite the entire volume (/dev/vg01/$myhost)...are you sure? " -n 1 -r
Here I only used ANSI-C quoting for the tab character. Make sure you don't leave any spaces between $'\t' and "This will....".
I ended up referencing this answer:
Read a variable in bash with a default value
and created a workaround. It's not perfect, but it works:
myhost="server1"
if [ -z $volreadexists ]; then
read -e -i "$myhost" -p $'\tJust checking if it\'s OK to overwrite volume at /dev/vg01/'
echo
if [[ $REPLY =~ ^$myhost[Yy]$ ]]; then
echo -e "\t\tContinuing"
else
echo "Fine...skipping"
fi
fi

How to change parameter in a file, only if the file exists and the parameter is not already set?

#!/bin/bash
# See if registry is set to expire updates
filename=hostnames
> test.log
PARAMETER=Updates
FILE=/etc/.properties
CODE=sudo if [ ! -f $FILE] && grep $PARAMETER $FILE; then echo "File found, parameter not found."
#CODE=grep $PARAMETER $FILE || sudo tee -a /etc/.properties <<< $PARAMETER
while read -r -a line
do
hostname=${line//\"}
echo $hostname":" >> test.log
#ssh -n -t -t $hostname "$CODE" >> test.log
echo $CODE;
done < "$filename"
exit
I want to set "Updates 30" in /etc/.properties on about 50 servers if:
The file exists (not all servers have the software installed)
The parameter "Updates" is not already set in the file (e.g. in case of multiple runs)
I am a little puzzled so far how, because I am not sure if this can be done in 1 line of bash code. The rest of the script works fine.
Ok, here's what i think would be a solution for you. Like explained in this article http://www.unix.com/shell-programming-scripting/181221-bash-script-execute-command-remote-servers-using-ssh.html
invoke the script which contains the commands that you want to be executed at the remote server
Code script 1:
while read -r -a line
do
ssh ${line} "bash -s" < script2
done < "$filename"
To replace a line in a text file, you can use sed (http://www.cyberciti.biz/faq/unix-linux-replace-string-words-in-many-files/)
Code script 2:
PARAMETER=Updates
FILE=/etc/.properties
NEWPARAMETER=Updates ###(What you want to write there)
if [ ! -f $FILE] && grep $PARAMETER $FILE; then exit
sed -i 's/$PARAMETER/$NEWPARAMETER/g' $FILE
So, I'm not certain this covers all your use case, I hope this helps you out if there is anything feel free to ask!

Resources