5

I'm looking for the simplest solution that takes $* as input, and expands to each element prefixed and suffixed with a given string:

$*=foo bar baz
<solution(x,y)>=xfooy xbary xbazy

I can do either prepending or appending, but not both:

echo ${*/#/x}
# prints xfoo xbar xbaz
echo ${*/%/y}
# prints fooy bary bazy

I'm unable to combine the two solutions. The documentation claims the value returned by the expansion in the parameter=* case is a list, but I'm unable to use it as such. I want to pass the resulting array of values to a further command as separate arugments, therefore simply building a single string wouldn't work.

  • 3
    The last paragraph of Stephane's answer is important. $* is almost certainly not what you want to be using; "$@" is the right thing when you want to deal with the argument list as a list of separate strings instead of converting it to a single string. – Charles Duffy Jul 22 at 23:30
  • Did you intend a solution for shells in general, or for bash? If bash, consider using the bash tag. – kojiro Jul 23 at 10:48
  • @kojiro: I'm going to use it in a specific shell context (either bash, zsh or ksh), but I'm also interested in a POSIX standard solution. – erenon Jul 23 at 11:11
3
#!/bin/bash
echo $*
FIELDS=("${@/#/x}")
FIELDS=("${FIELDS[@]/%/y}")
echo "${FIELDS[*]}"

When run:

$ t.sh foo bar baz
foo bar baz
xfooy xbary xbazy
| improve this answer | |
  • 2
    Note that ${var/pattern/replacement} is not a sh operator. I'd advise against giving a sh extension to a script which uses it. You'd also need to make sure the script is interpreted by a shell that supports it (ksh93, where it comes from or zsh/mksh/bash) by using the corresponding #! /path/to/shell - she-bang. – Stéphane Chazelas Jul 22 at 13:58
  • 1
    To emphasize that -- while many folks do use .sh extensions on non-POSIX shells, this is frowned on in the bash community. See the history of the relevant factoid from the freenode #bash IRC bot: wooledge.org/~greybot/meta/.sh; to quote its current content here: "Don't use extensions for your scripts. Scripts define new commands that you can run, and commands are generally not given extensions. Do you run ls.elf? Also: bash scripts are not sh scripts (so don't use .sh) and the extension will only cause dependencies headaches if the script gets rewritten in another language." – Charles Duffy Jul 22 at 23:32
  • ...it's the same as how a Python library gets a .py extension, but a Python executable has no extension at all (you run pip, not pip.py). A shell script should have an extension if it's intended to be sourced ("dotted") into an existing interpreter, but not if it's intended to be invoked as an executable. See also the essay at talisman.org/~erlkonig/documents/… – Charles Duffy Jul 22 at 23:34
  • Good info, thanks. FWIW, I'd also advise against naming a script "t" as well. – L. Scott Johnson Jul 23 at 11:31
10

${var/pattern/replacement} is a ksh93 parameter expansion operator, also supported by zsh, mksh, and bash, though with variations (mksh's currently can't operate on arrays).

ksh93

In ksh93, you'd do ${var/*/x\0y} to prefix the expansion of $var with x and suffix with y, and ${array[@]/*/x\0y} to do that for each element of the array.

So, for the array of positional parameters:

print -r -- "${@/*/x\0y}"

(beware however that like for your ${*/#/x}, it's buggy when the list of positional parameters is empty).

zsh

zsh's equivalent of ksh93's \0 to recall the matched string in the replacement is $MATCH, but only if you use (#m) in the pattern (for which you need the extendedglob option):

set -o extendedglob
print -r -- "${@/(#m)*/x${MATCH}y}"

But in zsh, you can nest parameter expansions, so you can also do:

print -r -- ${${@/#/x}/%/y}

Though you would probably rather use the $^array operator which turns on rcexpandparam for the expansion of that array, making it behave like brace expansion:

print -r -- x$^@y

Or you could use:

printf -v argv x%sy "$@"

To modify $@ (aka $argv in zsh) in-place (here assuming "$@" is not the empty list).

bash

In the bash shell, you'd probably need to do it in two steps with an intermediary array as shown by @L.ScottJohnson, or modifying $@ in place with:

set -- "${@/#/x}"
echo -E "${@/%/y}"

(here assuming the prefix (x in this case), doesn't start with -).

POSIXly

You could modify the positional parameters in-place with a loop:

for i do
  set -- "$@" "x${i}y"
  shift
done
echo "$@"

(though beware that echo can't be used portably to display arbitrary data that may contain backslash characters or start with -)

Note

Note that the $* form of parameter expansion (which is only useful quoted), is the one that is meant to concatenate the positional parameters (with the first character of $IFS, SPC by default). You need $@ (again, quoted) to expand to all positional parameters as separated arguments. Unquoted, $* and $@ make little sense (except in zsh where they expand to the non-empty positional parameters) as they would be subject to split+glob, and the behaviour varies between shells.

| improve this answer | |
2

Given a list in $@
... print it

set -- foo bar baz
printf '%s\n' "$@"
foo
bar
baz

... perform a list op

set -- $(printf 'x%sy ' "$@")
printf '%s\n' "$@"
xfooy
xbary
xbazy

... stringify list

printf '%s\n' "$*"
xfooy xbary xbazy

No special bash features involved.

| improve this answer | |
  • 1
    But you're using the split+glob operator, which means that won't work if the elements of the original list contain characters of $IFS or wildcard characters (try after set -- 'a b' '*' in a directory that contains a xutils.py file for instance). – Stéphane Chazelas Jul 22 at 22:32
  • I love the simplicity of this solution, but unfortunately set -- $(printf 'x%sy ' "$@") does not and cannot work reliably because of quoting and $IFS splitting problems. You need to use a for loop to be sure you are taking 1 arg input and transforming it into 1 arg output. Try set -- 'a b' 'p" "q' and try to convert that to 2 args xa by and xp" "qy and you will see. – Old Pro Jul 23 at 5:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.