0

Say, I have a string x which has multiple bash commands chained using command separators. E.g.

x="cmd1 2&>1 && cmd2 || cmd3 ; cmd4 | cmd5"

Is there a way I can easily split x into an array of strings y that contains all the individual bash commands and the command separators?

echo ${y[0]} --> cmd1 2&>1

echo ${y[1]} --> &&

echo ${y[2]} --> cmd2

echo ${y[3]} --> ||

echo ${y[4]} --> cmd3

echo ${y[5]} --> ;

echo ${y[6]} --> cmd4

echo ${y[7]} --> |

echo ${y[8]} --> cmd5

Note, cmd1, cmd2, etc can be any arbitrary bash command, e.g. ./test.sh arg1 arg2 or /bin/bash -c '...'

I tried using the IFS splitting method to separate the strings. However, when I set IFS='&&', it ends up splitting on a single & too. Example below:

> bash -c 'echo $(hostname)' 1>/dev/null 2>&1 && echo hello
hello
> x="bash -c 'echo \$(hostname)' 1>/dev/null 2>&1 && echo hello"
> echo $x
bash -c 'echo $(hostname)' 1>/dev/null 2>&1 && echo hello
> IFS='&&'
> cmds=($x)
> unset IFS
> echo ${cmds[0]}
bash -c 'echo $(hostname)' 1>/dev/null 2>
> echo ${cmds[1]}
1
> echo ${cmds[2]}

> echo ${cmds[3]}
echo hello
> echo ${cmds[@]}
bash -c 'echo $(hostname)' 1>/dev/null 2> 1 echo hello

As you can see above, IFS doesn't work when the separator is multiple characters.

Any ideas? Thanks!

Barmar
  • 741,623
  • 53
  • 500
  • 612
Jee Force
  • 23
  • 4
  • 5
    No, there's nothing simple for parsing shell commands. – Barmar May 27 '23 at 04:20
  • A prime example is `'echo \$(hostname)'` -- which will be word-split into `'echo` and `\$(hostname)'` unless you modify `IFS`. But, the point @Barmar is making is there is no simple way to handle all the caveats of what may be part of your `x=` and split that into an array where each element gets a command. Especially where you have whitespace in some of your commands. The time to handle separating the parts of `x=` is **before** the assignment to `x` is made. – David C. Rankin May 27 '23 at 07:36
  • 1
    Anyway, why would you want to do that? – KamilCuk May 27 '23 at 08:26
  • Note also that you don't even need whitespace delimiters. Many shell operators are delimiters by themselves. Your command can be written as `cmd1 2&>1&&cmd2||cmd3;cmd4|cmd5`. – Barmar May 27 '23 at 14:31
  • 1
    And you'll need to deal with quoting, and grouping with `{}` and `()`. – Barmar May 27 '23 at 14:33
  • `x="a=\; b='||' d $(printf '%s\n' e f | sort -r)"` ? – jhnc May 27 '23 at 14:51

1 Answers1

0

Is there a way I can easily split x into an array of strings y that contains all the individual bash commands and the command separators?

No.

Any ideas?

You have to write a parser for shell grammar or you can use an existing one. You may be interested in Bash and busybox ash and other shell sources and in shellcheck project.

Note: Completely unrelated to your program, when you want to store a command or chain of commands in something that can be executed or serialized, you should use a function. If you want to store command arguments in a variable, you should use a bash array. Related: https://mywiki.wooledge.org/BashFAQ/050 .

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Insetead of linking to an external site, how about this question: https://stackoverflow.com/questions/13365553/setting-an-argument-with-bash – Barmar May 27 '23 at 14:29