4

I'm having trouble understanding what I need to escape when using sh -c.

Let's say I want to run the for loop for i in {1..4}; do echo $i; done. By itself, this works fine. If I pass it to eval, I need to escape the $: eval "for i in {1..4}; do echo \$i; done", but I cannot make it work for sh -c "[...]":

$ sh -c "for i in {1..4}; do echo $i; done"
4

$ sh -c "for i in {1..4}; do echo \$i; done"
{1..4}

$ sh -c "for i in \{1..4\}; do echo \$i; done"
{1..4}

$ sh -c "for i in \{1..4\}\; do echo \$i\; done"
sh: 1: Syntax error: end of file unexpected

Where can I find more information about this?

2
  • 2
    Which shell is linked to your sh ? Commented Jul 11, 2022 at 22:51
  • 1
    which sh -> /bin/sh; file /bin/sh -> /bin/sh: symbolic link to dash Commented Jul 11, 2022 at 22:56

2 Answers 2

10

The usual wisdom is to define the script (after the -c) inside single quotes. The other part you need to use is a shell where the {1..4} construct is valid:

$ bash -c 'for i in {1..4}; do echo $i; done'  # also work with ksh and zsh

One alternative to get it working with dash (your sh) is to make the expansion on the shell you are using interactively (I am assuming that you use bash or zsh as your interactive shell):

$ dash -c 'for i do echo $i; done' mysh {1..4}
1
2
3
4
2

{1..4} is a zsh operator, extended from csh's {x,y} brace expansion operator now supported by a few other shells, but it's not a POSIX sh operator (until recently, POSIX actually even required echo {1..4} to output {1..4}).

Even in zsh or other shells that now support {x..y}, it's not the best way to do loops that increment an integer variable as you end up allocating a list of all the values (OK here for only 4 but doesn't scale well). for i in $(seq 4) (seq being not a standard command) has the same problem, in addition to relying on the current value of $IFS.

In ksh93, bash, zsh, using:

for (( i = 1; i <= 4; i++ )); do
  printf '%s\n' "$i"
done

Is generally regarded as better. It also allows negative values, and a better control on how the index is incremented.

The {x..y} approach has the advantage of allowing loops on several ranges, or looping on letters, or with padding (though with variations depending on the shell and version thereof) though:

for i in {1..5} {9..12} x{0..3} {f..p} {001..0010}; do
  ...
done

In zsh, see also:

i=; repeat 4 printf '%s\n' $((++i))

In POSIX sh, you'd do:

i=1; while [ "$i" -le 4 ]; do
  printf '%s\n' "$i"
  i=$(( i + 1 ))
done

Or:

sh -c '
  i=1; while [ "$i" -le 4 ]; do
    printf "%s\n" "$i"
    i=$(( i + 1 ))
  done'

From within another Bourne/rc/fish like shell if you need to have a new sh invocation run that loop.

Of course, here, you could also do:

for i in 1 2 3 4; do
  printf '%s\n' "$i"
done

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .