3

I'm reducing the question to (I believe) the simplest case. Let's say I have a script myscript.sh with the following contents:

#!/bin/bash
IFS='%20'
echo "$*"

If I run the command as follows, the output will look like:

me@myhost ~ $ ./myscript.sh fee fi fo fum
fee%fi%fo%fum

This is expected behavior, as described in the bash man page:

   *      Expands  to  the positional parameters, starting from one.  When
          the expansion occurs within double quotes, it expands to a  sin-
          gle word with the value of each parameter separated by the first
          character of the IFS special variable.  That is, "$*" is equiva-
          lent to "$1c$2c...", where c is the first character of the value
          of the IFS variable.  If IFS is unset, the parameters are  sepa-
          rated  by  spaces.   If  IFS  is null, the parameters are joined
          without intervening separators. 

However, what I would like to get is the output:

fee%20fi%20fo%20fum

Thus using a multiple character separator field rather than a single character.

Is there a way to do this that is native to bash?


UPDATE:

Based on the data from mikeserv below, and the writeup at Why is printf better than echo?, I ended up doing the following (again reduced to simplest case as in the example above):

#!/bin/bash
word="$1"
shift
if [ "$#" -gt 0 ] ; then
    word="$word$(printf '%%20%s' "$@")"
fi
printf '%s\n' "$word"
unset word
  • there is printf %s%%20 "$@" but that will do an extra one on the tail of the output. if you can be sure none of the args might be misinterpreted there is printf %b%%20 "$@\c". else you can do: for arg do shift; set -- "$@" %20 "$arg"; done; shift; printf %s "$@" – mikeserv Oct 12 '15 at 4:24
  • That's perfect! Add it as an answer with an explanation of the bits and pieces and I'll accept it. One more reason I need to learn printf and stop leaning on echo.... – Wildcard Oct 12 '15 at 4:42
  • word=$1${2+$(shift;printf %%20%s "$@")} - and you don't even have to lose $1 because it only gets shifted in the subshell. – mikeserv Dec 17 '15 at 4:21
5

printf applies its format string to each argument that follows it on output. It is a bash shell builtin and can be used to apply a delimiter string to a list of arguments - kind of.

For example:

printf %s:delimit: arg1 arg2 arg3

arg1:delimit:arg2:delimit:arg3:delimit:

The thing is, printf doesn't stop applying its format string at the end of its arguments, and so the last one gets an appended delimiter. This can be handled in some cases:

printf %b:delimit: \\0150 \\0145 \\0171\\c

h:delimit:e:delimit:y

printf interprets C and octal escapes as %bytes with a certain kind of format, and also with the %b format you \cut its output at a certain point, which is why printf does not follow the y above with a :delimit: string as its format string would otherwise indicate.

So if you want every argument literally interpreted and no trailing delimiter, then you have to address the problem within the argument list itself:

set -- arg1 arg2 arg3
for arg do shift
    set -- "$@" :delimit: "$arg"
done; shift
printf %s "$@"

arg1:delimit:arg2:delimit:arg3
2

In zsh, you can use j:string: Parameters Expansion flag:

set -- fee fi fo fum
delims=%20
print -rl ${(j:$delims:)@}
-1

If you use bash (or other shell with variable expansion enabled) and arguments do not have spaces inside you can do:

#!/bin/bash
line=$*
echo "${line// /:delimiter:}"

In the case of spaces you will have to use other field delimiter by setting IFS variable before line.

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.