1

I'm trying to pass an install command as an argument to bash script but this specific command segment curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh executing outside my terminal before it executes inside my script. Is there any solution for this. I need to run whatever the command I pass as arguments inside this script. Below is my implementation so far.

install_package () {
   $2
   EXITCODE=$?
   if [ "$EXITCODE" -ne "0" ]; then
       exit $EXITCODE
   fi
}

install_package "brew" "/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)""
Nipun Ravisara
  • 3,629
  • 3
  • 20
  • 35
  • 1
    quotes don't nest – jhnc Aug 06 '23 at 12:23
  • @jhnc thanks, then how can I achieve this functionality. any idea – Nipun Ravisara Aug 06 '23 at 12:25
  • 1
    `install_package(){ bash -c "$2" || exit $?; }; install_package "brew" "$(curl -fsSL ...)"` seems safer – jhnc Aug 06 '23 at 12:34
  • 1
    See: [Why does shell ignore quoting characters in arguments passed to it through variables](https://stackoverflow.com/questions/12136948/why-does-shell-ignore-quoting-characters-in-arguments-passed-to-it-through-varia) and [BashFAQ/050 - I'm trying to put a command in a variable, but the complex cases always fail!](http://mywiki.wooledge.org/BashFAQ/050) – tjm3772 Aug 06 '23 at 14:22
  • It is in general easier to expect the command as an array of the individual words than as a string. Also you are saying in the text of your question, that you need a bash _script_ for this purpose, but in your example code, you are using a bash _function_. Make up your mind, what you want to have; in particular when we talk about arrays, the difference matters. Finally, why aren't you ever using the frist argument in your function? – user1934428 Aug 07 '23 at 06:18

2 Answers2

1

I would use "$@" instead of cramming everything into $2:

install_package () {
   if ! "$@"; then exit $?; fi
}

Call it with any number of arguments. See your shell manual for the effect of quoting $@. Basically it keeps positional parameters $1...$n from being word-split.

Jens
  • 69,818
  • 15
  • 125
  • 179
  • 1
    Would add: if you want to pass things besides the command like OP appears to be doing, put them in the leading arguments (`$1`, `$2`, etc) and then remove them with `shift` before using `"$@"`. Also note that this only works for simple commands, you can't pass something like a redirection like this without some fiddling. – tjm3772 Aug 06 '23 at 14:41
0

There are a couple of issues with your code:

  1. Quotes don't nest. "a"b"c" is not a string containing a"b"c, it is a quoted a followed by an unquoted b followed by a quoted c.

  2. Even if you fixed (1), you try to parse the "command" (arg 2) multiple times.

  • First the code is obtained from curl and is (intended to be) stored in a string as an argument to a bash call.
  • Second, the function runs $2 which performs word-splitting. However, it will not do things like redirection, parameter expansion, variable assignments, and so on. c1 a; c2 when passed in becomes command c1 with two arguments, not c1 with one argument followed by another command c2.

For example:

$ v="a"b"c"
$ echo "$v"
abc
$ p()($1)
$ p "echo   hello   &&   echo   world   >/dev/null   &"
hello && echo world >/dev/null &
$ v='echo hello; n=10; echo $n'
$ p "$v"
hello; n=10; echo $n
$

The data returned by curl is a program, so it is appropriate to pass it to bash -c (assuming you trust it not to be malicious). However, to avoid multiple parses, it makes more sense to call bash only when you actually want to execute the code.

For example:

install_package(){
    bash -c "$2" || exit $?
}

Invoke as:

if code=$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh); then
    install_package "brew" "$code"
fi
jhnc
  • 11,310
  • 1
  • 9
  • 26