Call a command from a shell script, passing most arguments, allowing arguments with blanks

I am have a wrapper around a command sas that runs SAS code in batch. A typical call looks like this

./ -sysin /my_code/ -log /my_log_folder/my_program.log
  • passes all arguments to sas with ./sas $*.
  • sas then runs /my_code/ and writes the log to /my_log_folder/my_program.log.
  • Then analyzes the arguments it is called with
  • and copies the log to /admin/.hidden_log_folder/my_program_<today's date>.log

I want to make two changes:

Enable multi word parameters

Some customers absolutely want me to use blanks in folder and file names and demand me to run /their code/their, so if I run

./ -sysin "/their code/their" -log "/their log folder"

/their code/their and /their log folder should be passed single parameters to sas

Remove a specific parameter

Sometimes I need to run ./sas_utf8 in stead of ./sas and I am too lazy to maintain a second script, so I would like to allow an extra parameter, so that

./ -sysin /my_code/ -log /my_log_folder -encoding utf8

would call

./sas_utf8 -sysin /my_code/ -log /my_log_folder

instead of

./sas -sysin /my_code/ -log /my_log_folder

How can I do that, preferably in ksh?

Asked By: Dirk Horsten


First, use "$@" instead of $* (or $@) to keep the arguments intact. It expands each argument as a separate word, as if you used "$1" "$2"... Note that with $*, glob characters would also be a problem.

To look for the utf8-option, you could loop over the command line arguments, and copy the ones you want to keep to another array, and set a flag if you see -encoding and utf8.

Then just check the flag variable to determine which program to run, and pass "${sasArgs[@]}" to the command.


executable="./sas" # The default, for latin encoding

# Inspect the arguments,
# Remember where the log is written
# Change the executable if the encoding is specified
# Copy all arguments except the encoding to the 'sasArgs' array
while [[ "$#" -gt 0 ]]; do
    case "$1" in
            # change the executable, but do not append to sasArgs
            if [[ "$2" = "utf8" ]]; then
                shift 2
                echo "The only alternative encoding already supported is utf8" >&2
                exit 1
            # remember the next argument to copy the log from

#  To debug: print the args, enclosed in "<>" to discover multi word arguments
printf "Command and args: "
printf "<%s> " "$cmd" "${sasArgs[@]}"
printf "n"
# exit # when debugging

# Actually run it
"$executable" "${sasArgs[@]}"

# Copy the log using $logPath
# ...

The last printf calls print the arguments it would run, with <> around each, so you can check that the ones with spaces stay intact. (You could run echo "${sasArgs[@]}" but it couldn’t tell the two arguments foo and bar, from the single argument foo bar.)

If we were looking for a single argument, instead of a two-argument pair, that first part could be made a bit simpler with a for loop:

for arg in "$@" do
    case "$arg" in
            # change the executable, but do not append to the array

This could also be converted to plain POSIX sh. The for loop makes a copy of the list it’s given, so the copied arguments could be stored back in the positional parameters (appending with set -- "$@" "$arg") instead of using an array.

Also, the whole deal could be made a lot simpler if it was known the encoding arguments were at the beginning. Then it would be enough to check $1 (and $2), and they could be removed with shift.

(I tested the above scripts with Bash and the version of ksh93 I had on Debian. I’m not exactly that familiar with ksh, so I may have missed something. But Bash’s arrays are copied from ksh, so I expect it should work correctly in both.)

Answered By: ilkkachu
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.