25

How can I rewrite this command to only email if there is output from the mailq | grep?

mailq | egrep 'rejected|refused' -A 5 -B 5 | mail -s 'dd' email@email

Is this even possible on one line?

See Check if pipe is empty and run a command on the data if it isn't for a more general case than sending email.

cosmin
  • 351

8 Answers8

20

I think you will have to use a temp file for this operation so that you can use the && operator to only run the mail command if the grep returned an exit status that says it had matches like this:

TMPFILE=`mktemp /tmp/mailqgrep.XXXXXX`; mailq | egrep 'rejected|refused' -A5 -B5 > "$TMPFILE" && mail -s 'dd' email@email < "$TMPFILE"; rm "$TMPFILE"

If you didn't mind the temp file sticking around somewhere and can use a static name for it, you could skip the special naming and deletion stuff:

 mailq | egrep 'rejected|refused' -A5 -B5 > /tmp/mailqgrep && mail -s 'dd' email@email < /tmp/mailqgrep

Edit: After seeing glenn's answer I played with this some more and apparently assigning a variable using the $() syntax returns the exit code of the command, so you can skip the test he used for the string length and use that instead. Here it is all in one command:

data=$(mailq | egrep 'rejected|refused' -A 5 -B 5) && mail -s 'dd' email@email <<< "$data"

Edit 2: After seeing Simon's answer I checked out my mail program. It does not behave in the way he describes by default, but does have an option for that. From the man page:

-E If an outgoing message does not contain any text in its first or only message part, do not send it but discard it silently, effectively setting skipemptybody variable at program startup. This is useful for sending messages from scripts started by cron(8).

Making this possible:

mailq | egrep 'rejected|refused' -A 5 -B 5 | mail -E -s 'dd' email@email
Caleb
  • 70,105
  • 1
    the power of collaboration!! – glenn jackman May 17 '11 at 15:10
  • 3
    I would suggest this answer could be made better by moving the best solution up to the top. – Mark Booth May 18 '11 at 13:16
  • @Wildcard That edit was un-necessary, given the input parameters mktemp is not going to return something that needs quoting. – Caleb Nov 22 '16 at 05:46
  • 2
    I know. You are one of the few who bothers to understand that quotes are sometimes necessary (and omits them deliberately). However, the default should be quoting unless you explicitly want to call for glob(split(var)). You don't need word splitting or file glob expansion here, so don't ask for them. Also, we need to show the right way on this site. – Wildcard Nov 22 '16 at 05:59
  • @Wildcard Fair enough. I typically run zsh and am actually not getting globbing or word splitting in cases like this unless I ask for them, but for the sake of answers here and not knowing the target environment quoting out of principle is fine. – Caleb Nov 22 '16 at 06:05
13

The utility program ifne exists expressly for this purpose: to run a command if the standard input is not empty.

mailq | egrep 'rejected|refused' -A 5 -B 5 | ifne mail -s 'dd' email@email

The ifne command is part of the moreutils package, billed as "a growing collection of Unix tools that nobody thought to write long ago, when Unix was young."

8

You could save the output in a variable, and then invoke the mail command if the length of the string is non-zero.

data=$(mailq | egrep 'rejected|refused' -A 5 -B 5)
[[ -n "$data" ]] && mail -s 'dd' email@email <<< "$data"

For one line, join the two commands with a semi-colon.

glenn jackman
  • 85,964
  • 1
    Props for using a variable instead of a temp file. This got me thinking about where the exit code goes from the command that you did the variable assignment with, and apparently it gets passed on! See my updated answer. – Caleb May 17 '11 at 14:54
7

Use mail with the -E option. On my Mac, MAIL(1) says the -E flag will not send email with an empty body.

-E Do not send messages with an empty body. This is useful for piping errors from cron(8) scripts.

On my Mac, I ran the following test. Note that the file file_does_exist does exist, but the file file_does__not_exist does not exist.

This sends an email to me:

$ ls file_does_exist | egrep 'file' -A5 -B5 | mailx -E -s 'test' stefan@example.org

This does not. Note that the command ls file_does_not_exist | egrep ... does not produce any output.

$ ls file_does_not_exist | egrep 'file' -A5 -B5 | mailx -E -s 'test' stefan@example.org
ls: file_does_not_exist: No such file or directory
Stefan Lasiewski
  • 19,754
  • 24
  • 70
  • 85
5

In this particular case, if your mail supports the -E option, just use it. In the general case, you can try to read one character; if there is one, start the postprocessing command and feed it that character from the rest of the file.

pipe_if_not_empty () {
  a=$(dd bs=1 count=1 2>/dev/null; echo .)  # read at most one character
  if [ "$a" != "." ]; then                  # if there were two characters,
    { printf %s "${a%.}";                   # then output the first character
      cat; } |                              # and the rest of the input   
    "$@"                                    # into the specified program
  fi
}

mailq | egrep 'rejected|refused' -A 5 -B 5 |
pipe_if_not_empty mail -s 'dd' email@email

Note the useful use of cat. The addition of echo . makes this function work even if the first character in the input is a newline (remember that the $(…) construct strips terminal newlines).

With most shells (anything other than zsh, as far as I know), if the file begins with a null character, this code will believe it's empty. Fixing that is left as an exercise for the reader. (Hint: use od in the first subshell and printf to print the first byte.) (Solution: How to check if pipe is empty) You might run into the same issue if the file begins with a byte that is not a valid character in the current locale; that's easier to fix by running this code with LC_ALL=C.

  • +1 for ridiculous but possibly useful in another scenario :) Out of curiosity, why inject and test for the dot instead of testing for an empty string? Doesn't that introduce a problem if the input starts with a dot? Could using read help simplify this? – Caleb May 18 '11 at 13:20
  • @Caleb: Yes, I should have mentioned I was replying to the question in the title and not to the particular situation. See my edit for an explanation of the dot. I don't think you can distinguish an empty first line from an empty file with read in portable sh (it may be possible in bash, ksh or zsh). – Gilles 'SO- stop being evil' May 18 '11 at 15:00
3

IIRC the BSD mail command aborts if given an empty message.

1

Recently I have learned about the command name ifne which is part of the moreutils package. Where you can use it to solve this specific problem.

ifne - Run command if the standard input is not empty

example from the man page,

EXAMPLE
       find . -name core | ifne mail -s "Core files found" root
Rabin
  • 3,883
  • 1
  • 22
  • 23
0

You could rewrite your command using test -s /dev/stdin to check if there is any output from the mailq | grep part.

- mailq | egrep 'rejected|refused' -A 5 -B 5 | mail -s 'dd' email@email
+ mailq | egrep 'rejected|refused' -A 5 -B 5 | (test -s /dev/stdin && cat) | mail -s 'dd' email@email
trent55
  • 57