8

This command, when run alone, produces the expected result (the last line of the crontab):

tail -n 1 /etc/crontab

However, when I run it as part of an echo command to send the result to a file, it adds a summary of all the files in the working directory, plus the expected result:

sudo bash -c 'echo $(tail -n 1 /etc/crontab) > /path/to/file'

Why did this command produce the extra data?

3

3 Answers 3

22

Your crontab line has one or more asterisks * in it, indicating "any time". When that line is substituted in from the command substitution, the result is something like

echo * * * * * cmd > /path/to/file

While most further expansions are not applied to the output of command substitution, pathname expansion is (as is field splitting):

The results of command substitution shall not be processed for further tilde expansion, parameter expansion, command substitution, or arithmetic expansion. If a command substitution occurs inside double-quotes, field splitting and pathname expansion shall not be performed on the results of the substitution.

Pathname expansion is what turns *.txt into a list of matching filenames (globbing), where * matches everything. The end result is that you get every (non-hidden) filename in the working directory listed for every * in your crontab line.


You could fix this by quoting the expansion, if the code you posted was a representative of a more complex command:

sudo bash -c 'echo "$(tail -n 1 /etc/crontab)" > /path/to/file'

but more straightforwardly just lose the echo entirely:

sudo bash -c 'tail -n 1 /etc/crontab > /path/to/file'

This should do what you want and it's simpler as well (the only other material difference is that this version will omit field splitting that would otherwise have occurred, so runs of spaces won't be collapsed).

1
  • 5
    Since /etc/crontab is almost always world readable, all the "bash -c" trickery is actually unnecessary: tail -n -1 /etc/crontab | sudo tee /path/to/file is the idiom that I've found to be the least error prone when redirecting output into files that require superuser privileges.
    – Bass
    Commented Apr 16, 2019 at 8:22
5

Let's consider a directory with these files:

$ ls
crontab  file1  file2  file3
$ cat crontab
f*

Now, let's run the tail command:

$ tail -n 1 crontab
f*

The above is the last line of crontab and this is what we expect. However:

$ echo $(tail -n 1 crontab)
file1 file2 file3

Double-quotes eliminate this problem:

$ echo "$(tail -n 1 crontab)"
f*

Without the double-quotes, the result of the command substitution is expanded by the shell. One of the expansions is pathname expansion. In the case above, this means that f* is expanded to match every file name that starts with f.

Unless you explicitly want shell expansions, put all you shell variables and/or command substitutions inside double quotes.

4

globing shell mecanism will expand * to local file.

crontab line is likely to have a * as placeholder for any.

e.g. this line in crontab run on 7.47 am on sunday, first star mean any day, second any month.

47  7 * * 0 /run/on/sunday

then you tail, and issue

echo 47  7 * * 0 /run/on/sunday

that will expand * to local file.

You must log in to answer this question.

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