Shell – How to Pass a Command with Arguments to a Script

shellzsh

I am trying to write a shell script that receives as an input a command with arguments and runs it.

As an example, I am hoping to use this in cron as follows:

0 11 * * * my_wrapper.sh "task_name" "command arg1 arg2 arg3 ..."

The details of what my_wrapper.sh does don't matter, but it is a zsh script and I want it to receive command arg1 arg2 arg3 ... and invoke it. Note that the arguments may contain single quotes, double quotes, etc.

What is the proper way of passing and receiving commands with arguments to scripts?

Update:

On the command line in zsh, @Gilles' first solution works great:

#!/bin/zsh
task_name=$1
shift
"$@" > /path/to/logging_directory/$task_name

and then invoking > my_wrapper.sh date "%Y-%b-%d" from the command line does the job.

However, when I try to use it as follows in cron:

CRON_WRAPPER="/long/path/to/my_wrapper.sh"
0 11 * * * $CRON_WRAPPER "current_date.log" date "+%Y-%b-%d"

It doesn't work.

Final update (problem solved):

As explained in Gilles' answer, crontab requires escaping any % signs. After changing the above to:

CRON_WRAPPER="/long/path/to/my_wrapper.sh"
0 11 * * * $CRON_WRAPPER "current_date.log" date "+\%Y-\%b-\%d"

it worked. All set.

Best Answer

You have two choices: you can pass a program to execute with some arguments, or you can pass a shell script. Both concepts can be called “a command”.

A program with some arguments takes the form of a list of strings, the first of which is the path to an executable file (or a name not containing any slash, to be looked up in the list of directories indicated by the PATH environment variable). This has the advantage that the user can pass arguments to that command without worrying about quoting; the user can invoke a shell explicitly (sh -c … if they want). If you choose this, pass each string (the program and its argument) as a separate argument to your script. These would typically be the last arguments to your script (if you want to be able to pass more arguments, you need to designate a special string as an end-of-program-arguments marker, which you then can't pass to the program unless you make the syntax even more complicated).

0 11 * * * my_wrapper.sh "task_name" command arg1 arg2 arg3 ...

and in your script:

#!/bin/zsh
task_name=$1
shift
"$@"

A shell script is a string that you pass to the sh program for execution. This allows the user to write any shell snippet without having to explicitly invoke a shell, and is simpler to parse since it's a single string, hence a single argument to your program. In an sh script, you could call eval, but don't do this in zsh, because users wouldn't expect to have to write zsh syntax which is a little different from sh syntax.

0 11 * * * my_wrapper.sh "task_name" "command arg1 arg2 arg3 ..."

and in your script:

#!/bin/zsh
task_name=$1
sh -c "$2"