Question

I'm working with a utility (unison, but that's not the point) that accepts parameters like:

$ unison -path path1 -path path2 -path path3

I would like to write a sh script that I could run like this:

$ myscript path1 path2 path3

I'm hoping for a Posix compliant solution, but bash-specific would also be good.

I'm guessing it should be something like:

#!/bin/sh
unison ${*/ / -path }

But this doesn't work.

EDIT: OK, I think I got something:

#!/bin/bash
PARAMS=
for arg in "$@"
do
    PARAMS+=" -path '$arg'"
done
unison $PARAMS

The problems are this only works in bash, and I'm pretty sure there's a better way to quote the parameters.

Was it helpful?

Solution

Unchecked, it could be as simple as:

exec unison -path $1 -path $2 -path $3

If you don't embed spaces in your path names, then you can deal with a variable number of arguments with:

arglist=""
for path in "$@"
do
    arglist="$arglist -path $path"
done
exec unison $arglist

If you have spaces in your path names, then you have to work a lot harder; I usually use a custom program called escape, which quotes arguments that need quoting, and eval:

arglist=""
for path in "$@"
do
    path=$(escape "$path")
    arglist="$arglist -path $path"
done
eval exec unison "$arglist"

I note that using Perl or Python would make handling arguments with spaces in them easier - but the question asks about shell.

It might also be feasible in Bash to use a shell array variable - build up the arguments into an array and pass the array as the arguments to the unison command.

OTHER TIPS

If you use Bash's arrays, all your quoting problems go away.

#!/bin/bash
args=()
for i in "$@"; do
    # With Bash >= 3:
    args+=(-path "$i")
    # +=() doesn't work in Bash 2
    # args=("${args[@]}" -path "$i")
done
exec unison "${args[@]}"

In Bash, you can use "${@/#/-path }" which will replace the beginning of each positional parameter with "-path ". To represent the end of the string use % instead of #.

Here is a simple demo script using sed and repeated -e options. (Of course there are more efficient ways to use sed.)

#!/bin/bash
echo "Resulting arguments: ${@/#/-e }"
sed "${@/#/-e }"

And running it like this:

$ echo abc | demo s/a/A/ s/b/B/

We get:

Resulting arguments: -e s/a/A/ -e s/b/B/
ABc

If you want a heavily bash-specific version, you can try

#! /bin/sh

eval eval exec \
  unison -path\\ \\\"{$(eval echo \\\"\\\${1..$#}\\\" | sed 's/ /,/g')}\\\"

If you remove all the triple-backslash-quoted characters, this becomes easier to understand, but I won't spoil the fun by explaining it :-)

The main complication is to handle file names with spaces. That accounts for the triple-backlash quoting and double eval.

Here is how you can satisfyingly quote your characters:

job_strategy()
{
  local p
  for p in "$@"; do
    printf '-path\000%s\000' "$p"
  done
}

job_process()
{
  xargs -0 unison
}

job_strategy "path1" "path2" "path3" | job_process
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top