Shell Scripting: Calling mailx in a loop - shell

I am trying to write a script using which I need to send multiple emails with a file as attachment per email. This is because of mail attachment size limitations.
I have zip files in a directory and they are file01.zip, file02.zip etc. and there will be about 4-5 of these files.
-- File count is normally passed in
numFiles=5
fileCounter=1
datestr="`date +"%m/%d/%Y"`"
while [ $fileCounter -le $numFiles ]
do
SUBJECT_LINE="Weekly files ($fileCounter of $numFiles) - $datestr"
echo "[`date`] E-mailing file ($fileCounter of $numFiles) ... "
ZIPFILE="file0$fileCounter.zip"
echo $ZIPFILE
ls -ltr $ZIPFILE
mailx -a "$ZIPFILE" \
-r no-reply#host.com \
-s "$SUBJECT_LINE" \
$TO_LIST < /dev/null
echo "[`date`] Done"
fileCounter=$(( $fileCounter + 1 ))
done
I am trying to call mailx in a loop as you can see. I tried the following as well
for file in file0*.zip
do
...
done
I am able to see the ZIPFILE names when I print them out using echo but the mailx command in the loop returns the following although the files are there:
No such file or directory
I can run the same mailx command from console and have the e-mail sent out. I can also send one e-mail without a loop, but doing so inside a loop seems to cause an issue. Am I missing something?

I likely had one or more characters not visible to the eye in the file name ($ZIPFILE) being passed in as attachment to mailx. I typed parts of the script today again while troubleshooting and that fixed the issue. But the script above is good.

Related

how to compare same file and send mail in linux

I am creating bash script on a file to send diff over the mail.
for below case, I have created two files as "xyz.conf" and "xyz.conf_bkp" to compare
So far, I have come with below script -
file="/a/b/c/xyz.conf"
while true
do
sleep 1
cmp -s $file ${file}_bkp
if [ $? > 0 ]; then
diff $file ${file}_bkp > compare.log
mailx -s "file compare" abc#xyz.com < compare.log
sleep 2
cp $file ${file}_bkp
exit
fi
done
I have scheduled above script to run every second
* * * * 0-5 script.sh
This is working fine but I am looking with different approach like below -
I am looking for to work without creating another backup file Imagine if I have to work multiple files which will lead me to crate those many backups which doesn't look good solution.
Can anyone suggest, how to implement this approach?
I would write it this way.
while cmp "$file" "${file}_bkp"; do
sleep 2
done
diff "$file" "${file}_bkp" | mailx -s "file compare" abc#xyz.com
cp "$file" "${file}_bkp"
I wanted to avoid running both cmp and diff, but I'm not sure that is possible without a temporary file or pipe to hold the data from diff until you determine if mailx should run.
while diff "$file" "${file}_bkp"; do
sleep 2
done | {
mailx -s "file compare" abc#xyz.com
cp "$file" "${file}_bkp"
exit
}
diff will produce no output when its exit status is 0, so when it finally has a non-zero exit status its output is piped (as the output of the while loop) to the compound command that runs mailx and cp. Technically, both mailx and cp can both read from the pipe, but mailx will exhaust all the data before cp runs, and this cp command would ignore its standard input anyway.

Creating a concatenated file in unix then mailing that file to you all in the same script

I am trying to create a script which will concatenate all the out.* files in the directory /home/rfranklin/stackDump/ and then pipe that concatenated file to a mailx command - so I can mail it to myself
I've so far tried two methods and neither of them seem to be working. Hoping someone can tell me why!
So far in the /home/rfranklin/stackDump/ directory I have the files:
out.file1
out.file2
out.file3
out.file4
otherfile.txt
otherfile2.txt
First of all I tried to write a for loop:
#!/bin/bash
#
OUT_FILES_DIRECTORY="/home/rfranklin/stackDump/out.*"
for file in $OUT_FILES_DIRECTORY
do
cat $file > stack_dump_`date +%Y%m%d` | mailx -s stack_dump_`date +%Y%m%d` rfranklin#gmail.com
done
This returns:
Null message body; hope that's ok
Null message body; hope that's ok
Null message body; hope that's ok
Null message body; hope that's ok
And I receive 4 blank emails. BUT the concatenated file is created so I know something is working.
Next I tried to use a here document:
#!/bin/bash
#
bash <<'EOF'
cd /home/rfranklin/stackDump
cat out.* > stack_dump_`date +%Y%m%d` | mailx -s stack_dump_`date +%Y%m%d` rfranklin#gmail.com
done
EOF
This does not work for me either. Where am I going wrong!
Thanks
I don't see the point of creating a file here, you could just as easily pipe the output of cat to mailx
cat /home/rfranklin/stackDump/out.* |
mailx -s "stack_dump_$(date +%Y%m%d)" rfranklin#gmail.com
If you prefer an attachment to content in the mail body
cat /home/rfranklin/stackDump/out.* |
uuencode "stack_dump_$(date +%Y%m%d)" |
mailx -s "stack_dump_$(date +%Y%m%d)" rfranklin#gmail.com
You can use tee for this:
#!/bin/bash
d=$(date +%Y%m%d)
for file in /home/rfranklin/stackDump/out.*
do
cat "$file" | tee -a "stack_dump_$d" | mailx -s "stack_dump_$d" rfranklin#gmail.com
done
tee copies standard input to a file, as well as to standard output. The -a option appends to the file rather than overwriting it.
In your original version of the script, the > was redirecting the output of cat to the file, which meant that the pipe to mailx was empty.
I am assuming that your script doesn't run over more than one day, so I have moved the calls to date outside the loop.
From what I understand, you want to concatenate all files in a given directory into one file, and then mail that to yourself. Correct me if I'm wrong.
So first concatenate all files into one:
cat /home/rfranklin/stackDump/out.* > concatFile
Then mail it to yourself:
dat=$(date +%Y%m%d)
mail -s "stack_dump_$dat" rfranklin#gmail.com < concatFile
Edit
You can put it in a script:
dir=$1
cat $dir/out.* > concatFile
dat=$(date +%Y%m%d)
mail -s "stack_dump_$dat" rfranklin#gmail.com < concatFile
run it as so:
./script /home/rfranklin/stackDump
The concatFile will be created in your current directory.

Printing shell script doesn't print anything

I'm trying to create a print service von my raspberry pi. The idea is to have a pop3 account for print jobs where I can sent PDF files and get them printed at home. Therefore I set up fetchmail & rarr; procmail & rarr; uudeview to collect the emails (using a whitelist), extract the documents and save them to /home/pi/attachments/. Up to this point everything is working.
To get the files printed I wanted to set up a shell script which I planned to execute via a cronjob every minute. That's where I'm stuck now since I get "permission denied" messages and nothing gets printed at all with the script while it works when executing the commands manually.
This is what my script looks like:
#!/bin/bash
fetchmail # gets the emails, extracts the PDFs to ~/attachments
wait $! # takes some time so I have to wait for it to finish
FILES=/home/pi/attachments/*
for f in $FILES; do # go through all files in the directory
if $f == "*.pdf" # print them if they're PDFs
then
lpr -P ColorLaserJet1525 $f
fi
sudo rm $f # delete the files
done;
sudo rm /var/mail/pi # delete emails
After the script is executed I get the following Feedback:
1 message for print#MYDOMAIN.TLD at pop3.MYDOMAIN.TLD (32139 octets).
Loaded from /tmp/uudk7XsG: 'Test 2' (Test): Stage2.pdf part 1 Base64
Opened file /tmp/uudk7XsG
procmail: Lock failure on "/var/mail/pi.lock"
reading message print#MYDOMAIN.TLD#SERVER.HOSTER.TLD:1 of 1 (32139 octets) flushed
mail2print.sh: 6: mail2print.sh: /home/pi/attachments/Stage2.pdf: Permission denied
The email is fetched from the pop3 account, the attachement is extracted and appears for a short moment in ~/attachements/ and then gets deleted. But there's no printout.
Any ideas what I'm doing wrong?
if $f == "*.pdf"
should be
if [[ $f == *.pdf ]]
Also I think
FILES=/home/pi/attachments/*
should be quoted:
FILES='/home/pi/attachments/*'
Suggestion:
#!/bin/bash
fetchmail # gets the emails, extracts the PDFs to ~/attachments
wait "$!" # takes some time so I have to wait for it to finish
shopt -s nullglob # don't present pattern if no files are matched
FILES=(/home/pi/attachments/*)
for f in "${FILES[#]}"; do # go through all files in the directory
[[ $f == *.pdf ]] && lpr -P ColorLaserJet1525 "$f" # print them if they're PDFs
done
sudo rm -- "${FILES[#]}" /var/mail/pi # delete files and emails at once
Use below to filter the pdf files in the first place and then you can remove that if statement inside the for loop.
FILES="ls /home/pi/attachments/*.pdf"

If-else-statement is working wrong in crontab?

When i use this:
*/5 6-18 * * 1-6 [ "$(ls -A /DIR_WHERE_FILES_ARE_OR_NOT/)" ] &&
rsync -au /DIR_WHERE_FILES_ARE_OR_NOT/ /DIR_WHERE_FILES_SHOLD_GO; \
mv /DIR_WHERE_FILES_ARE_OR_NOT/* /SAVE_DIR/ ||
mail -s "DIR IS EMPTY" myemail#klkldkl.de <<< "message"
i get two mails:
mv: cannot stat `/DIR_WHERE_FILES_ARE_OR_NOT/*': No such file or
directory
and
"DIR IS EMPTY"
Why?
You get
mv: cannot stat `/DIR_WHERE_FILES_ARE_OR_NOT/*': No such file or directory
for exactly the reason stated: that directory is empty, hence it does not contain a file named * (asterisk). It's just the way glob expansion works in the shell: if the glob doesn't match anything it is passed literally to the command. Since mv attemps to rename a non-existing file, it complains as shown.
This would all be much more readable, if instead of a sequence of && and || operators in a crontab you would place the whole logic in a script with equivalent if/else/fi constructs and just call the script from cron.
You get two mails because you explicitly send the first with mail -s. The second is from cron because the output on stderr and stdout is not empty.
Your commands are equivalent to
if [ "$(ls ...)" ]; then
rsync
fi
if ! mv; then
mail
fi
Note that there is no else.
Just like user Jens already mentioned, and also from my experience, unless you are using a very simple and usually single command, you should stick to script files. So, in your case, I would go with a script file. I'll give you an example.
#!/bin/bash
dir_where_files_are_or_not=/filespath
dir_where_files_should_go=/another/filespath
save_dir=/savefiles/path
# ok, lets start by checking if dir contains files
if [ "$(ls -A $dir_where_files_are_or_not)" ]; then
# dir contains files, so lets rsync and mv them
rsync -au $dir_where_files_are_or_not/ $dir_where_files_should_go
mv $dir_where_files_are_or_not/* $save_dir
else
# dir is empty, lets send email
mail -s "DIR IS EMPTY" myemail#klkldkl.de <<< "message"
fi
Now, I just put this code in a file. Give it a name, for example "chkfiles" and save it in a directory (I use /usr/local/sbin for all of my scripts).
Next, in a shell, run the command chmod +x /usr/local/sbin/chkfiles to make the file executable. Then add the script to your crontab.
I would suggest the following line inside crontab:
*/5 6-18 * * 1-6 /bin/bash /usr/local/sbin/chkfiles
I used /bin/bash to call the right interpreter for this script. It should now work as expected.
Important Notes:
Before running the script, you need to change the dir_where_files_are_or_not, dir_where_files_should_go and save_dir vars to your needs.
Do NOT include trailing slashes in the dirs, otherwise the rsync and mv might not do what you really want
Regards
You get two mails because when mv fails, cron captures what is written to standard error and mails it to the owner, then runs the mail command. You can suppress the error message from mv to avoid the mail from cron.
mv /DIR_WHERE_FILES_ARE_OR_NOT/* /SAVE_DIR/ 2> /dev/null || mail -s "DIR IS EMPTY" myemail#klkldkl.de <<< "message"

Create a detailed self tracing log in bash

I know you can create a log of the output by typing in script nameOfLog.txt and exit in terminal before and after running the script, but I want to write it in the actual script so it creates a log automatically. There is a problem I'm having with the exec >>log_file 2>&1 line:
The code redirects the output to a log file and a user can no longer interact with it. How can I create a log where it just basically copies what is in the output?
And, is it possible to have it also automatically record the process of files that were copied? For example, if a file at /home/user/Deskop/file.sh was copied to /home/bckup, is it possible to have that printed in the log too or will I have to write that manually?
Is it also possible to record the amount of time it took to run the whole process and count the number of files and directories that were processed or am I going to have to write that manually too?
My future self appreciates all the help!
Here is my whole code:
#!/bin/bash
collect()
{
find "$directory" -name "*.sh" -print0 | xargs -0 cp -t ~/bckup #xargs handles files names with spaces. Also gives error of "cp: will not overwrite just-created" even if file didn't exist previously
}
echo "Starting log"
exec >>log_file 2>&1
timelimit=10
echo "Please enter the directory that you would like to collect.
If no input in 10 secs, default of /home will be selected"
read -t $timelimit directory
if [ ! -z "$directory" ] #if directory doesn't have a length of 0
then
echo -e "\nYou want to copy $directory." #-e is so the \n will work and it won't show up as part of the string
else
directory=/home/
echo "Time's up. Backup will be in $directory"
fi
if [ ! -d ~/bckup ]
then
echo "Directory does not exist, creating now"
mkdir ~/bckup
fi
collect
echo "Finished collecting"
exit 0
To answer the "how to just copy the output" question: use a program called tee and then a bit of exec magic explained here:
redirect COPY of stdout to log file from within bash script itself
Regarding the analytics (time needed, files accessed, etc) -- this is a bit harder. Some programs that can help you are time(1):
time - run programs and summarize system resource usage
and strace(1):
strace - trace system calls and signals
Check the man pages for more info. If you have control over the script it will be probably easier to do the logging yourself instead of parsing strace output.

Resources