LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Bash script help (https://www.linuxquestions.org/questions/programming-9/bash-script-help-864756/)

Diggy 02-24-2011 08:07 AM

Bash script help
 
Hello, all.

I'm creating a bash script to check how much free space is left in /var directory then, if it hits a certain threshold, delete certain files with numbers for extensions (e.g. fileA.1, fileA.2 fileA.3, and fileA.4, fileB.1, fileB.2 fileB.3, and fileB.4 ). Here's a snippet from my script:

if [ $PARTUSE -ge $ALERT ]; then
cat /dev/null > /var/log/fileA.* 2>&1 &&
cat /dev/null > /var/log/fileB.* 2>&1 &&
cat /dev/null > /var/log/fileC.* 2>&1 &&
echo "Reclaimed disk space on \"$PARTITION\" now ($PARTUSE% full) on $(hostname) as of $(date)" |
mail -s "Reclaimed disk space - $PARTITION on $(hostname)" $ADMIN
fi

If I use a * as a wildcard for the number extension, the script fails. Maybe regex would work here, but I'm not particularly accomplished at it. Or some other construct.

Your help would be most appreciated.

Diggy

colucix 02-24-2011 08:32 AM

You cannot redirect the standard output to multiple files at once. You can use a loop to empty every single file, e.g.
Code:

for file in /var/log/fileA.? /var/log/fileB.? /var/log/fileC.?
do
  > $file
done

Note you don't need cat /dev/null. In bash/ksh the redirection symbol alone creates/overwrite a file as if the null command was executed. Hope this helps.

MCD555 02-24-2011 08:32 AM

Quote:

Originally Posted by Diggy (Post 4269636)
Hello, all.

I'm creating a bash script to check how much free space is left in /var directory then, if it hits a certain threshold, delete certain files with numbers for extensions (e.g. fileA.1, fileA.2 fileA.3, and fileA.4, fileB.1, fileB.2 fileB.3, and fileB.4 ). Here's a snippet from my script:

if [ $PARTUSE -ge $ALERT ]; then
cat /dev/null > /var/log/fileA.* 2>&1 &&
cat /dev/null > /var/log/fileB.* 2>&1 &&
cat /dev/null > /var/log/fileC.* 2>&1 &&
echo "Reclaimed disk space on \"$PARTITION\" now ($PARTUSE% full) on $(hostname) as of $(date)" |
mail -s "Reclaimed disk space - $PARTITION on $(hostname)" $ADMIN
fi

If I use a * as a wildcard for the number extension, the script fails. Maybe regex would work here, but I'm not particularly accomplished at it. Or some other construct.

Your help would be most appreciated.

Diggy

The colucix is the right approach...

colucix 02-24-2011 08:34 AM

Quote:

Originally Posted by MCD555 (Post 4269653)
Just try using the command:

Code:

cat /dev/null > /var/log/fileA.\*
It's should be works!

Nope. To me it creates a literal fileA.* since escaping prevents expansion.

Diggy 02-24-2011 08:40 AM

colucix,

I used a poor example (apologies) in decribing what I want to do. I really want to delete files along the lines of Afile.1, Afile.2, Bfile.1, Bfile.2. Given that, How would I write the construct you suggested if, in fact, it would still work in this case?

Diggy

MCD555 02-24-2011 08:49 AM

Quote:

Originally Posted by Diggy (Post 4269662)
colucix,

I used a poor example (apologies) in decribing what I want to do. I really want to delete files along the lines of Afile.1, Afile.2, Bfile.1, Bfile.2. Given that, How would I write the construct you suggested if, in fact, it would still work in this case?

Diggy

And why you don't just use a rm command for that?
Too risky?
May it's not so clear (to me!) the phrase

Quote:

I really want to delete files along the lines of Afile.1, Afile.2, Bfile.1, Bfile.2
sorry!

Diggy 02-24-2011 08:56 AM

I don't want to destroy the files, just empty them of content.

Diggy

someshpr 02-24-2011 09:11 AM

Quote:

Originally Posted by Diggy (Post 4269679)
I don't want to destroy the files, just empty them of content.

Diggy

As colucix suggested
Code:

> $file
should only empty the files, not delete them.

colucix 02-24-2011 09:12 AM

Quote:

Originally Posted by Diggy (Post 4269679)
I don't want to destroy the files, just empty them of content.

Diggy

This actually empties the content of the file:
Code:

> file
What are your doubts about the suggested loop?

Diggy 02-24-2011 10:23 AM

colucix,

I'm sure that your solution 1) works, and 2) is "elegant". I (ashamedly) just don't have a lot of scripting experience. That said, would my entire script look like this?:

#!/bin/bash
#
# Shell script to reclaim disk space
# It will clear out certain files if the percentage of partition space used is >= % set in ALERT variable,
# thus reducing partition size, and will send an email to the system administrator(s)
# -------------------------------------------------------------------------
# Written by Me
# -------------------------------------------------------------------------
#
# set admin email address(es)
ADMIN="root@localhost"
#
# set alert level at 90%. If used space exceeds this, send alert
ALERT=80
#
# check partition space and, if necessary, send alert
df -H | grep '/var' | awk '{ print $5 " " $6 }' | while read output;
do
PARTUSE=$(echo $output | awk '{ print $1}' | cut -d'%' -f1 )
PARTITION=$(echo $output | awk '{ print $2 }' )
if [ $PARTUSE -ge $ALERT ]; then
for file in /var/log/Afile.? /var/log/Bfile.? /var/log/Bfile.?
do
> $file
done &&
echo "Reclaimed disk space on \"$PARTITION\" now ($PARTUSE% full) on $(hostname) as of $(date)" |
mail -s "Reclaimed disk space - $PARTITION on $(hostname)" $ADMIN
fi
done


Please bear with me, I'm learning, and this will be invaluable.

Diggy

colucix 02-24-2011 11:19 AM

Quote:

Originally Posted by Diggy (Post 4269775)
Please bear with me, I'm learning, and this will be invaluable.

Hey, no problem at all! I was just trying to clarify! :)

Regarding your script it looks good. I would avoid some redundancy, anyway. For example, to extract the usage of the /var partition you might use a more compact code. In place of:
Code:

df -H | grep '/var' | awk '{ print $5 " " $6 }' | while read output
do
  PARTUSE=$(echo $output | awk '{ print $1}' | cut -d'%' -f1 )
  PARTITION=$(echo $output | awk '{ print $2 }' )

you can simply do:
Code:

df -P | awk '/\/var/{ sub(/%/,"",$5); print $5, $6 }' | while read PARTUSE PARTITION
do

Notes:
  1. the -H option of df is not really necessary, since you're not intersted in the actual sizes but only in the percentage. Instead the -P option is useful, since it causes the df output to be in a more standard format. There are situations where the device name is very long and a line of output can be splitted in two parts. The -P option prevents this behaviour.
  2. awk is a very powerful language that manages regular expressions, so that the grep command is not necessary. Moreover it can remove the % sign, so that you can avoid the cut command.
  3. the read statement accept multiple variable names. The input will be splitted to fill all the specified variables, so that you can assign PARTUSE and PARTITION simultaneously.

Finally I don't see the need for a while loop, since you're reading a single line. To assign the output of a command using the read statement, process substitution is your friend:
Code:

read PARTUSE PARTITION < <(df -P | awk '/\/var/{ sub(/%/,"",$5); print $5, $6 }')
Hope this helps! :)

Diggy 02-24-2011 11:52 AM

What you suggest looks just fine, but I'm not sure how the final product would look. Based on my knowledge, and the suggestions in your last post, I'm not seeing where/how the files are being concatenated.

I understand that this piece:

df -P | awk '/\/var/{ sub(/%/,"",$5); print $5, $6 }' | while read PARTUSE PARTITION
do

replaces:

df -H | grep '/var' | awk '{ print $5 " " $6 }' | while read output
do
PARTUSE=$(echo $output | awk '{ print $1}' | cut -d'%' -f1 )
PARTITION=$(echo $output | awk '{ print $2 }' )

After the "do" statement, do I place my email alert piece? What handles the actual file concatenation?

I'm a bit confused, and feeling not just a little stupid.

Diggy

colucix 02-24-2011 12:02 PM

It simply replaces the extraction of information from the df output and it does the assigment to PARTUSE and PARTITION in a different way, but the rest stays untouched:
Code:

#!/bin/bash
#
# Shell script to reclaim disk space
# It will clear out certain files if the percentage of partition space used is >= % set in ALERT variable,
# thus reducing partition size, and will send an email to the system administrator(s)
# -------------------------------------------------------------------------
# Written by Me
# -------------------------------------------------------------------------
#
# set admin email address(es)
ADMIN="root@localhost"
#
# set alert level at 90%. If used space exceeds this, send alert
ALERT=80
#
# check partition space and, if necessary, send alert
read PARTUSE PARTITION < <(df -P | awk '/\/var/{ sub(/%/,"",$5); print $5, $6 }')

if [ $PARTUSE -ge $ALERT ]; then
  for file in /var/log/fileA.? /var/log/fileB.? /var/log/fileC.?
  do
    > $file
  done &&
  echo "Reclaimed disk space on \"$PARTITION\" now ($PARTUSE% full) on $(hostname) as of $(date)" |
  mail -s "Reclaimed disk space - $PARTITION on $(hostname)" $ADMIN
fi


Diggy 02-24-2011 12:24 PM

I apologize, again. I didn't mean to have you write this for me. But, now I get it. I'll test in a while, and report back. Thanks for your patience!!

Diggy

colucix 02-24-2011 12:30 PM

You're welcome! :)

prowla 02-24-2011 01:07 PM

You don't need awk and all that to get the info out of the "df"; this shows how:

Code:

df -P | while read fs kblocks used avail perc mount
do
  echo ${perc%%\%} $mount
done


Diggy 02-24-2011 02:19 PM

colucix,

It worked! It does raise another question for me, though.

I tried the same script on two machines. Machine 1 had files Afile.1 through .4, and Bfile.1 through .4, and Cfile.1 through .4. Machine 2 had only Afile.1 through .4. All of the files on Machine 1 were concatenated, just as I wanted. And, on Machine 2, Afile.1 through .4 were concatenated. But, files named Bfile.? and Cfile.? were created in the directory. Of course, I don't want that to happen.

So, in order to use the same script on different machines where any combination of Afiles, Bfiles, and Cfiles might exist, what should I do so that "missing" files are ignored. Hopefully, with modifications, your construct can be made to do that. BTW, the use of Afile, Bfile, and Cfile are purely illustrative. They could be named Curly.1, Moe.1, and Larry.1, etc.

As ever, thanks.

Diggy

someshpr 02-24-2011 02:34 PM

Quote:

Originally Posted by Diggy (Post 4270033)
colucix,

It worked! It does raise another question for me, though.

I tried the same script on two machines. Machine 1 had files Afile.1 through .4, and Bfile.1 through .4, and Cfile.1 through .4. Machine 2 had only Afile.1 through .4. All of the files on Machine 1 were concatenated, just as I wanted. And, on Machine 2, Afile.1 through .4 were concatenated. But, files named Bfile.? and Cfile.? were created in the directory. Of course, I don't want that to happen.

So, in order to use the same script on different machines where any combination of Afiles, Bfiles, and Cfiles might exist, what should I do so that "missing" files are ignored. Hopefully, with modifications, your construct can be made to do that. BTW, the use of Afile, Bfile, and Cfile are purely illustrative. They could be named Curly.1, Moe.1, and Larry.1, etc.

As ever, thanks.

Diggy

To ignore "missing" files, you can do existence check like this:
In colucix's script, instead of
Code:

for file in /var/log/fileA.? /var/log/fileB.? /var/log/fileC.?
  do
    > $file
  done

Try:
Code:

  for file in /var/log/fileA.? /var/log/fileB.? /var/log/fileC.?
  do
    if [ -f $file ]; then
      > $file
    fi
  done


Diggy 02-24-2011 03:01 PM

Ah, a conditional statement! Worked like a charm.

Thanks to you all for your help. This made my day!

Best,

Diggy

colucix 02-24-2011 03:34 PM

Another way is by means of the nullglob option:
Code:

shopt -s nullglob
for file in /var/log/fileA.? /var/log/fileB.? /var/log/fileC.?
do
  > $file
done

From the bash reference manual:
Quote:

nullglob
If set, Bash allows filename patterns which match no files to expand to a null string, rather than themselves.

unSpawn 02-24-2011 04:00 PM

Quote:

Originally Posted by Diggy (Post 4269662)
I really want to delete files

If services log to file then logrotate is the answer (and not script) if you want to retain logging. If you do not want to retain logging then do not log (as much as you do now).

colucix 02-24-2011 04:28 PM

Quote:

Originally Posted by unSpawn (Post 4270164)
If services log to file then logrotate is the answer (and not script) if you want to retain logging. If you do not want to retain logging then do not log (as much as you do now).

Good point, unSpawn! I was too focused on the shell issues to think about the logical thing to do. :doh:

Diggy 02-24-2011 04:43 PM

UnSpawn of rkhunter fame? Excellent. I use that great program!

You're right about logrotate, but I need to use the script, regardless. Let me elucidate (finally got to use that word :-) ).

The box I'm running the script on is old - old OS, old hardware, small disk. Because so much log activity takes place, the /var directory fills up about every four weeks because of the growth of certain log files. Logrotate rotates them fine, but can't keep up with the growth. I suppose I could change the rotation to a shorter period, or do it based on file size, but I'll be doing a hardware refresh to a bigger, better box in about eight weeks, so the script will be a stopgap measure. And, I'm not concerned about deleting precious log files; they're parsed on a continuous basis for events of interest. Finally, rather than giving this man a fish, you've helped teach him how to fish, thereby feeding him for live.

My gratitude.

Diggy

unSpawn 02-24-2011 05:40 PM

Quote:

Originally Posted by colucix (Post 4270191)
too focused on the shell issues to think about the logical thing to do.

Heh, well, as it appears from the OP's explanation the logical thing to do isn't always the right thing to do...

unSpawn 02-24-2011 05:45 PM

Quote:

Originally Posted by Diggy (Post 4270205)
I use that great program!

Good to know it's of use.


As we're scripting away here's another take on things zapping everything (or so I'd hope) bigger than half a GB in /var/log:
Code:

/usr/sbin/lsof -Pwln +D/var/log|awk '/log\// {print $NF}'|sort -u|while read LOG; do
 [ `stat -c %s "${LOG}"` -gt $[10240*50] ] && $>${LOG}
done

this may or may not be as you like it and it's not tested so YMMV(VM).

* Whatever you do you should 'kill -HUP $PID' afterwards to allow processes to re-open file descriptors.

Diggy 02-25-2011 08:11 AM

unSpawn,

As an aside, the box that this script will be running on has provided great service to us for nearly seven years. As mentioned, it'll be peacefully retired in about two months. I've tried to take all of the necessary precautions in case it fails but, God willing, it'll make it to the finish line.

The last construct you showed me is interesting, and well beyond my scripting level. I'll study it, and test it on a throw-away VM to see how it performs.

Many thanks.

Diggy

unSpawn 02-25-2011 11:32 AM

Quote:

Originally Posted by Diggy (Post 4270890)
the box that this script will be running on has provided great service to us for nearly seven years. As mentioned, it'll be peacefully retired in about two months. I've tried to take all of the necessary precautions in case it fails but, God willing, it'll make it to the finish line.

A proper burial would be a bit over the top but I hope you give its remnants a good final destination?..


Quote:

Originally Posted by Diggy (Post 4270890)
The last construct you showed me is interesting, and well beyond my scripting level. I'll study it, and test it on a throw-away VM to see how it performs.

It's simple: run lsof and only show items in /var/log | awk for any line that has "log/" in it and print the last field (file name) | sort output showing unique items only | while read the output line by line into the variable "LOG"; and for each item do
[ `stat the item only returning its size in bytes` and check if the size is greater than $[10240*50] ] if it is then empty the item
done.


All times are GMT -5. The time now is 02:09 AM.