Bash: Preserving Whitespace Using set and eval

If you don't care much about whitespace bash is great: it normally turns multiple whitespace characters into one and it breaks things into words based on white space. If on the other hand you'd like to preserve whitespace bash can be a bit difficult at times. A trick which often helps is using a combination of bash's eval and set commands.

Let's say that you're building a list of items where each item may contain significant spaces, say something like:

#!/bin/bash

items=
for i in "$@"
do
    items="$items \"$i\""
done

for i in $items
do
    echo $i
done

But when you run this and try to use the items from the saved list you don't quite get what you expected:

  $ sh t1.sh "ab cd" "ef gh"
  "ab
  cd"
  "ef
  gh"

One solution is to do the following:

#!/bin/bash

items=
for i in "$@"
do
    items="$items \"$i\""
done

eval set -- $items
for i in "$@"
do
    echo $i
done

Which produces the desired result:

  $ sh t2.sh "ab cd" "ef gh"
  ab cd
  ef gh

The important line is:

  eval set -- $items

The set command takes any arguments after the options (here "--" signals the end of the options) and assigns them to the positional parameters ($0..$n). The eval command executes its arguments as a bash command.

If you do this without the eval command you'll get the same result as the first example. By passing the set command to eval bash will honor the embedded quotes in the string rather than assume they are part of the word.

If you run this script you can see a bit more of what bash is doing:

#!/bin/bash

items=
for i in "$@"
do
    items="$items \"$i\""
done

set -x
set -- $items
set +x
echo '===='
set -x
eval set -- $items
set +x

This produces:

  $ sh t3.sh "ab cd" "ef gh"
  + set -- '"ab' 'cd"' '"ef' 'gh"'
  + set +x
  ====
  + eval set -- '"ab' 'cd"' '"ef' 'gh"'
  ++ set -- 'ab cd' 'ef gh'
  + set +x

Mitch Frazier is an embedded systems programmer at Emerson Electric Co. Mitch has been a contributor to and a friend of Linux Journal since the early 2000s.

Load Disqus comments