2

TL/DR: I'm working in Solaris 10. I have a ls ... | egrep ... command, and I need to know if it outputs any results or not. I could just add a | wc -c to the end; but I need the result (0 or non-zero) to be in the exit code, not in the output. And I can't use if, it's not a bash script, I can only execute a single command.


Long version: I'm writing a maintenance process to compress and remove old log files in a Solaris 10 system. It checks all the .log or .xml files inside a given path, takes the ones which were last modified on a given month, creates a .tar with them, and then removes the original files:

ls -Egopqt /path/ | egrep -i '2016-10-[0123][0-9] .*(\.log$|\.xml$)' | awk '{ print $7 }'
| xargs tar -cvf target.tar

And the same to remove the files, just replacing the last part with: | xargs -i rm {}

I'm probably overcomplicating it, but it works. Unless there are no files for a given month; if that's the case, I get an error saying tar: Missing filenames. How can I check it before attempting to create the tar? I thought of something like this, using wc to check if there is an output or not:

ls ... | egrep ... | wc -c

Which correctly outputs 0 when there aren't any files, and another number otherwise. The problem is: I can't see the output, only the exit code (which is always 0 since there is no error). I'm not doing this in a bash script, I'm working with Siebel CRM: I have a javascript function which generates the commands and executes them with Clib.system calls. The only thing I can see is the exit code: 0 for OK, non-zero for an error (I see the actual number, not "non-zero").

Previously I had a similar requeriment, to check if a single file exists or not, and this answer helped me to get to this:

[ -f filename ] && exit 111 || exit 0

I'm successfully getting either 111 or 0, depending on if filename exists or not. But I can't get it to work with the ls ... | egrep ... | wc command. I've tried using this syntax:

[[ $( ls -Egopq /path/ | egrep -i ... | wc -c ) -ne 0 ]] && exit 111 || exit 0

But I'm getting always exit code 2, it doesn't matter if there are files or not. What am I doing wrong?


PS: I know I could write a tiny shell script to perform the checks, use a simple if to compare the output, and then return whatever exit code I want. Also, I actually can access a command's output, I'd just need to redirect it to a > tempfile and then read it from Siebel. However, I'd prefer to avoid both of these options, as I'm trying to avoid creating unnecessary (temp or permanent) files.

3 Answers 3

6

egrep returns non-zero if no lines were matched.

3
  • Unbelieveable... I've been trying commands for a few hours, and I didn't think of that!?
    – AJPerez
    Commented Nov 22, 2016 at 17:34
  • I seem to remember having the same feeling when I first discovered this feature myself :D
    – Muzer
    Commented Nov 22, 2016 at 17:35
  • Ah yes that is another alternative, just add set -o pipefail;....;set +o pipefail. Commented Nov 22, 2016 at 17:35
1

From xargs manual:

--no-run-if-empty
-r     If  the standard input does not contain any nonblanks, do not run the command.  
       Normally, the command is run once even if there is no input.  This option is a GNU extension.

Seems like doing ... | xargs -r tar -cvf target.tar should do the trick !

2
  • Thanks! However, my Solaris xargs version doesn't have that option. Anyway, I also need to know if there were files or not, in order to stop further actions, not only to avoid the tar creation
    – AJPerez
    Commented Nov 22, 2016 at 17:38
  • Ok, I will leave this here for the sake of completeness, but in your case Muzer's solution should work! Commented Nov 22, 2016 at 17:39
1

The ls command is fine-tuned for presenting information to users, which means it's not so great for generating output that can be parsed reliably.

Consider using find instead, which means you can probably get rid of grep as well.

For example, delete all the files whose name ends with .DeleteMe and that are older than a week:

find /path -name '*.DeleteMe' \
           -mtime +6 \
           -exec rm -f -- {} +

If you want to see what it's about to do, replace -exec rm with -exec echo rm.

Unlike any approach involving xargs (*1), this approach has the advantage that it won't do weird things if it encounters filenames with weird characters in them.

If you want to take some other action when there are no matching files, it would be simple enough to write the list of files to a temporary file, and then check if that file is empty.

find /path -name '*.DeleteMe' \
           -mtime +6 \
           -exec rm -f -- {} + \
           -print > list_of_files$$
if ! [ -s list_of_files$$ ] ; then
    echo There were no matching files
fi
rm -f list_of_files$$

If you need to perform some additional action before the first file, then

find /path -name '*.DeleteMe' \
           -mtime +6 \
           -exec sh -c '
               o=$0   # at_least_one$$ from the outer shell
               [ -e $o ] || do_before_stuff
               > $o
               for f do
                   do_something with "$f"
               done
           ' at_least_one$$ {} +
if [ -e at_least_one$$ ] ; then
    do_after_stuff
else
    do_no_files_stuff
fi
rm -f at_least_one$$

This is of course a simplified example. If you're doing anything complicated, then instead of sh -c 'code here', consider writing a script and invoking that instead.

For your particular problem I would write:

find /path/ -name '2016-10-[0123][0-9]*' \
             \( -name '*.log' -o -name '*.xml' \) \
            -exec tar -cvf target.tar {} +

I note that this has the same failure mode as every other method shown so far: if the list of files is too long for the command line, tar will be invoked repeatedly, which will probably result in a tarball that only contains the last few files.

To get around this, I suggest using cpio instead:

find /path/ -name '2016-10-[0123][0-9]*' \
             \( -name '*.log' -o \
                -name '*.xml' \) \
            -print |
cpio -o > target.cpio.trial
if [ -s target.cpio.trial ]
then mv target.cpio.trial target.cpio
else rm target.cpio.trial
fi

Some versions of cpio support -H format, which means you can tell it to write a tarball that can be extracted using tar; otherwise you'll need cpio to extract the files when you want them (unless your version of tar can understand cpio-format archives).

(footnote *1: okay, if you have GNU find and GNU xargs, then find ... -print0 | xargs -0r cmd ... is equally as reliable as find ... -exec cmd {} +, but if you have those you have other even better options.)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .