1

In bash, brace expansion is performed as a shell operation before executing the command.1 This means the following will work:
echo {1..10} >output | at -m now

The output will be 1 2 3 4 5 6 7 8 9 10.

What does not work is an attempt to perform brace expansion within the at command which warns that commands will be executed by /bin/sh, which in my case is linked to dash which does not seem to support range expansion using braces.

$ at - now
warning: commands will be executed using /bin/sh
at> echo {1..10} >output
at> <EOT>

This will run the command in dash (or Bourne or whatever shell you have /bin/sh linked to) resulting in the output {1..10}.

How can brace expansion be performed when using the at command this way?


I am interested in seeing other solutions to this besides the one I've posted as an answer, such as being able to execute commands with bash from within the at prompt. It seems like more effort than necessary to create a script for every single line command that is run from the at prompt.

2 Answers 2

3

There are three straightforward other options. One is a bit fragile and relies on implementation details of the shells, another uses here documents to provide the script to bash inline, and the other just uses -c to pass a single line in.


Most reliably and portably, use a here document:

$ at now
warning: commands will be executed using /bin/sh
at> bash <<'EOF'
at> echo {1..5} >/tmp/output
at> EOF
at> <EOT>
job 24 at Mon Mar 23 20:41:20 2015
$ cat /tmp/output
1 2 3 4 5

<<word declares a here document that is provided as standard input to bash and lasts until word. By quoting 'EOF' the document is not subject to expansion by the shell, so you can write a $ in the body without dash eating it. Your script can include anything other than EOF (or your word of choice) at the start of a line.

This is really the approach I'd recommend — you can very nearly write it exactly as though you'd been using bash all along, with just an extra line at the start and end to show there's anything going on.


If it's really just a single line, just use bash -c to pass a single command to run:

$ at now
warning: commands will be executed using /bin/sh
at> bash -c 'echo {1..3} >/tmp/output'
at> <EOT>
job 25 at Mon Mar 23 20:50:03 2015
$ cat /tmp/output
1 2 3

That can be a bit of a hassle when you have quotes to use inside the command as well.


Most hackishly: atd generally sends jobs to the shell as standard input (but I don't believe it's required to). Shells generally read standard input line-by-line (but they are not required to behave in that way).

If your implementations do behave that way then you can perform a nasty trick to run your job under another shell:

$ at now
warning: commands will be executed using /bin/sh
at> bash
at> echo {1..10} >/tmp/output
at> <EOT>
job 22 at Mon Mar 23 20:40:00 2015
$ cat /tmp/output
1 2 3 4 5 6 7 8 9 10

This works because sh reads the first line and executes bash, and bash then gets to read the rest of standard input as its commands. This is pretty fragile, but is the least intrusive version when it works.

dash itself is notably fiddlier in this area than many shells, and filling up an internal buffer is more important to it. Other simplistic shells, such as BusyBox's sh, tend to have the line-buffering behaviour.


Finally, although you're never required to use a separate script file, in many cases it actually will be the better option rather than trying to be too clever about things. When your jobs are single-use or programmatically-generated, these are reasonable options, but any other time sucking it up and using a standalone script is probably worth it in maintenance terms.

I really can't recommend the stdin exploitation of the last option at all, but it's a cute trick.

2
  • Thanks for taking the time. That "here document" method is exactly the type of solution I was seeking. I knew there was something like bash -c, but I was really looking for the freedom to redirect not just to bash, but any unforeseen scenario in the future that is similar. Portability being a big bonus.
    – iyrin
    Mar 23, 2015 at 8:48
  • And, if you're lazy, you can use the "here document", but end it with <EOT>, skipping over the EOF. It will give you an error message, but it seems to work. Mar 23, 2015 at 9:32
0

The only solution I've found so far is to write a script (let's call it expandme.sh) which specifies the use of bash as the interpreter.

#!/bin/bash
echo {1..10} >output

Then, run the script from the at prompt:

$ at - now
warning: commands will be executed using /bin/sh
at> expandme.sh
at> <EOT>

Now the script will get us out of the dash/Bourne exile that at put us in and perform the expansion before dumping the output.

You must log in to answer this question.

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