Mind the Arguments

How command line arguments are processed in Unix is important to know when scripting. That which should be a simple script is often troublesome to debug if the arguments to some of the commands get mangled unexpectedly.

To help understand what ends up being sent as command line arguments to a command, we can use a little Perl script I wrote to see exactly what the arguments are and how they were broken up by the shell. It does nothing more that print each argument out on a line of its own, but just that little service is enough to learn the finer points of command line argument processing. I called this script sargs, and here is the code:

#!/usr/bin/env perl
foreach $arg (@ARGV) {
  print("$arg\n");
}

Let us take a look at it in action in a few different circumstances. First with three arguments:

% sargs 1 2 3
1
2
3

Now with the same three arguments combined as one:

% sargs '1 2 3'
1 2 3

We all know that quoting an argument allows us to put spaces and other special characters within them, and with sargs we can see it first hand. With one more simple example, let us see shell wildcards in action:

% sargs /*
/bin
/boot
/cdrom
/dev
/etc
/home
/lib
/lost+found
/mnt
/opt
/proc
/root
/sbin
/service
/sys
/tmp
/usr
/var

This is the evidence that it is the shell, not the command, that interprets the wildcard asterisk (*) and expands it into many arguments, one for each file with a matching name.

Now let us use sargs for something more useful. What happens to command line arguments in a command issued with SSH? SSH mangles the command line when used to issue shell commands. How? We can see exactly how. Three arguments apart are seen as normal:

% ssh localhost sargs 1 2 3
1
2
3

But if I group them inside quotes, something does not work right:

% ssh localhost sargs '1 2 3'
1
2
3

Those were supposed to be one argument! What SSH does is it joins all the command line arguments it receives and passes the string to a shell for interpretation, which in this case includes parsing the one argument as separate arguments. Thus, to keep those arguments together we need to make sure that the quotes are seen by SSH by escaping them:

% ssh localhost sargs "'1 2 3'"
1 2 3

That is what we wanted. Since SSH joins all the arguments together, we can even send them to SSH as separate arguments as long as the quotes are escaped:

% ssh localhost sargs \'1 2 3\'
1 2 3

So next time you have trouble with running commands over SSH remember that the shell gets to see the SSH command and run it and that it is not run directly by the sshd process. Let us take a quick look at sudo to see if it behaves similarly:

% sudo sargs '1 2 3'
1 2 3

There is no reprocessing by the shell here. This is good most of the time when you want to run sudo for a single command because there are no shell double-interpreting surprises. This is bad if you want to run two commands with one invocation of sudo, like this:

% sudo sargs 1 2 '&&' sargs 3 4
1
2
&&
sargs
3
4

Oops! That second command got processed as arguments of the first! Normally it is recommended that you invoke sudo twice and not quote the double-ampersand like so:

% sudo sargs 1 2 && sudo sargs 3 4
1
2
3
4

But sometimes you cannot do that, such as if the first command is sleep and you cannot allow the second sudo command to request your password again because you will be at lunch (sleep 900; shutdown -r now). Sure, you could use at, but that can be messy in its own way for some tasks. So how can we invoke sudo so that it can run two commands?

The answer is you cannot, but you can invoke sudo to run a command that runs two commands. What command might that be? A shell invoked as a command, of course! All of the shells that I know of use the -c flag for this. If you provide a -c flag, the next argument is expanded as a shell command and you can use the special shell characters to break the command up into multiple commands:

% sudo zsh -c 'sargs 1 2 && sargs 3 4'
1
2
3
4

It is not as pretty as one might hope, but it is reasonably easy to do.

I hope that if you had trouble in the past with figuring out how arguments are handled by different programs that you take this chance to learn using sargs. It can really come in handy some day when you are programming that script that you need “an hour ago” but cannot quite figure out why it does not work right. If that happens to you, just whip out sargs and verify that your commands are seeing their arguments exactly as they are supposed to see them, and if not then escape or unescape the offending special characters appropriately!

  • You can skip to the end and leave a comments. Trackback is currently closed.
  • Trackback URI: http://cosine.org/2007/09/22/mind-arguments/trackback/
  • Comments RSS 2.0

Leave a Reply

You must be logged in to post a comment.