Call a command from a shell script, passing most arguments, allowing arguments with blanks
I am have a wrapper run_sas.sh
around a command sas
that runs SAS code in batch. A typical call looks like this
./run_sas.sh -sysin /my_code/my_program.sas -log /my_log_folder/my_program.log
run_sas.sh
passes all arguments tosas
with./sas $*
.sas
then runs/my_code/my_program.sas
and writes the log to/my_log_folder/my_program.log
.- Then
run_sas.sh
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 program.sas
, so if I run
./run_sas.sh -sysin "/their code/their program.sas" -log "/their log folder"
/their code/their program.sas
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
./run_sas.sh -sysin /my_code/my_program.sas -log /my_log_folder -encoding utf8
would call
./sas_utf8 -sysin /my_code/my_program.sas -log /my_log_folder
instead of
./sas -sysin /my_code/my_program.sas -log /my_log_folder
How can I do that, preferably in ksh
?
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.
So:
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
-encoding)
# change the executable, but do not append to sasArgs
if [[ "$2" = "utf8" ]]; then
executable="./sas_u8"
shift 2
continue
else
echo "The only alternative encoding already supported is utf8" >&2
exit 1
fi
;;
-log)
# remember the next argument to copy the log from
logPath="$2"
;;
esac
sasArgs+=("$1")
shift
done
# 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
-encoding-utf8)
# change the executable, but do not append to the array
executable="./sas_u8"
continue
;;
esac
sasArgs+=("$arg")
done
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.)