2

I have a bash file that executes $@ in the end

$@

And it receives chained commands a single argument.

./script.sh "ls && clear"

It executes first command & consider rest as it's arguments.
I want to execute all the chained commands as they normally execute.

Dhirendra
  • 780
  • 9
  • 26
  • 3
    You don't. `"$@"` (it should be quoted) only works because after it expands to a series of words, the shell identifies the first word as the command to run, and all the remaining words are the *arguments* to the command. Parsing of shell syntax like `&&` happens before parameter expansion, so it's too late to recognize more complicated command lines. – chepner Oct 10 '18 at 13:43
  • BTW, your code needs to be `"$@"`, not unquoted `$@`, to work correctly with arguments containing whitespace, glob expressions, or other nontrivial content. – Charles Duffy Oct 10 '18 at 13:59
  • Possible duplicate of [eval command in Bash and its typical uses](https://stackoverflow.com/questions/11065077/eval-command-in-bash-and-its-typical-uses) – Corion Oct 10 '18 at 14:02
  • See the `eval` built-in of bash – Corion Oct 10 '18 at 14:02
  • @Corion, I **strongly** disagree that `eval` is appropriate here. `eval $@` or even `eval "$@"` has some very buggy behavior, because it's concatenating all your arguments into a single string and parsing it together. – Charles Duffy Oct 10 '18 at 14:03
  • 2
    @Corion, ...consider `./script.sh printf '%s\n' *` -- go ahead and try it as just `"$@"`, and again with `eval`; you'll see behavior that's very different. (But *don't* try it if you're in a directory where you don't control the filenames; if someone created a file with `touch '$(rm -rf ~)'`...) – Charles Duffy Oct 10 '18 at 14:05
  • @Corion, ...`eval "$1"` might be appropriate (because it's passing through exactly one string to be parsed as code), especially with something like `script=$1; shift; eval "$script"` letting other arguments be referred to as `$1`, `$2`, etc., and thus providing a way to pass data out-of-band from code; `eval "$@"` basically never is. – Charles Duffy Oct 10 '18 at 14:11

2 Answers2

3

Maintaining The "$@" Interface

If you want shell syntax (compound commands, redirections, etc), you need to invoke a shell. Thus:

./script.sh bash -c 'ls && clear'

...you'll also need to change $@ to "$@" (unquoted, $@ is exactly the same as -- meaning, has all the bugs of -- $*).


If you want to pass data, do so with extra arguments out-of-band from your code to avoid security bugs:

dir=/tmp # but unlike eval, this is still safe with *any* directory name
./script.sh bash -c 'ls "$1" && clear' _ "$dir"

Unlike some other approaches, this still lets ./script.sh printf '%s\n' "first line" "second line" work.


Accepting Only One Argument

Using "$@" promises to the user that multiple arguments will be processed; however, eval "$*", like bash -c "$*", discards distinctions between those arguments, whereas bash -c "$@" silently ignores arguments past the first unless the first argument is written with knowledge of that calling convention.

To only accept one argument, and parse it as code, use:

eval "$1"

To only accept the first argument as code, and keep subsequent arguments as $1, $2, etc. in the context of that code, use:

code=$1; shift; eval "$code"
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Thanks @Charles, but arguments are not in my control. – Dhirendra Oct 10 '18 at 14:28
  • 1
    So, what *is* the possible range of arguments? Do you **ever** need to handle more than one argument? What are the cases other than `ls && clear` that need to be correctly handled, so we can be sure we don't break them in providing something that fixes the one case you did give us? – Charles Duffy Oct 10 '18 at 14:36
  • There is only one argument, a string. It may contain any number of commands in chain & commands can have arguments. Ex: "python manage.py migrate && python manage.py collectstatic" – Dhirendra Oct 10 '18 at 16:49
  • Then `eval "$1"` will do; if it's guaranteed that there's only one argument, there's no reason to use `"$@"` at all. And starting an extra copy of `bash` is just silly. – Charles Duffy Oct 10 '18 at 16:57
1

You can use the following command on the sh file:

#!/usr/bin/env bash

bash -c "$@"
Miguel
  • 1,361
  • 1
  • 13
  • 24
  • 2
    `bash -c "$@"` only runs the first argument in `"$@"` as a script, then passes the remainder as arguments to that script. That's fine for `"ls && clear"`, since it's only one string (so we're leaving the arguments for `$0`, `$1`, etc empty), but `ls foo` would need to be changed to `"ls foo"`, which arguably defeats the whole purpose of using `"$@"`. – Charles Duffy Oct 10 '18 at 14:01
  • If you execute it, you will see that execute both, at least in my terminal – Miguel Oct 10 '18 at 14:06
  • Yes, because of the quotes forcing both elements into the first argv element. My point is that you made the quotes mandatory, so the way `"$@"` used to work (where you could pass multiple arguments through) no longer does. It's just like how Python's `shell=True` ignores list elements other than the first unless the string in the first element explicitly refers to `$0`, `$1`, etc. – Charles Duffy Oct 10 '18 at 14:07
  • 1
    See `./script.sh printf '%s\n' "first line" "second line"` as an example that works with the original `"$@"`, but doesn't work with `bash -c "$@"`. – Charles Duffy Oct 10 '18 at 14:09
  • Well, my answer was with the intention to keep the public interface the author expose on the question. He wanted to execute multiple commands using one single string with double quote, so I was adjusting the answer on what he asked. – Miguel Oct 10 '18 at 14:10