printargs.c – debugging utility for quoted strings in shell scripts

November 12, 2009

Here’s a very simple utility program that I use to debug how the shell interpreter parses quoted arguments in shell scripts. Shell quoted strings can be one of the trickiest things to get right in a script. White-space characters in filenames and directory names, string concatenation, nested single, double, back-tick quotes are only a few of the complications!

Note: To compile the code    $ cc printargs.c -o printargs

Here’s an example of debugging using printargs:
  $ cp $file $target

This works fine as long as the $file or $target name does not contain any white space characters. But if they do, the command will fail. For example: turn on trace ‘set -x’ and set $file=’Star Trek IV.mp4′. The trace will display ‘+ cp Star Trek IV.mp4 destdir’ which looks right, but actually is wrong. Let’s prefix the cp command with printargs to see why.

$ file=’Star Trek IV.mp4′; target=destdir
$ printargs cp $file $target
+ printargs cp Star Trek IV.mp4 destdir
1 : ‘cp’
2 : ‘Star’
3 : ‘Trek’
4 : ‘IV.mp4’
5 : ‘destdir’

The copy command is actually trying to copy three files {Star, Trek, IV.mp4} to the dest directory. It should be just one file (‘Star Trek IV.mp4’). The fix is to quote both $file and $target ie: cp “$file” “$target”.

$ file=’Star Trek IV.mp4′; target=destdir
$ printargs cp “$file” “$target”
1 : ‘cp’
2 : ‘Star Trek IV.mp4’
3 : ‘destdir’

After debugging, remove the printargs prefix from the command and the script should be good to go.

/* Name:        %I%
 * Synopsis:    printargs -c -i -q commandline
 * Description: printargs displays cmdline arguments.  This is useful for debugging 
 *              shell scripts that escape the space, backslash, quote chars ('"`)
 *              and other wildcard characters (eg: "[*?]").
 *                Options: -c  Echo the command line
 *                         -i  Do not display argument indices
 *                         -q  Do not display single quotes surrounding each argument
 */

#include 
#include 

main(int argc, char *argv[])
{
        int opt;
        extern int optind;
        int echo_cmdline = 0; 
        int display_indices = 1; 
        int indx;
        char *quotes = "'";
        char *spaces = "";

        while ((opt = getopt(argc, argv, "ciq")) != EOF) {
                switch(opt) {
                case 'c': 
                        echo_cmdline = 1;
                        break;
                case 'i':
                        display_indices = 0;
                        break;
                case 'q':
                        quotes = "";
                        break;
                default:
                        fprintf(stderr, "%s: Invalid option '%c'\n", opt);
                        fprintf(stderr, "Syntax: %s [-ciq] command command_options command_args ..."
);
                        exit(1);
                }
        }

        --argc;
        if (echo_cmdline == 1) {
                printf("CmdLine: ");
                for (indx = optind; indx <= argc; ++indx)
                        printf("%s%c", argv[indx], indx < argc ? ' ' : '\n');
        }

        for (indx = optind; indx <= argc; ++indx) {
                if (display_indices == 1)
                        printf("%d : ", indx - optind + 1);
                printf("%s%s%s\n", quotes, argv[indx], quotes);
        }

        exit(0);
}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: