Naftuli Kay
Naftuli Kay

Reputation: 91630

Making a copy of command line arguments inside a function

Currently at work on the following version of Bash:

GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu)

My current script:

#!/usr/bin/env bash

function main() {
  local commands=$@
  for command in ${commands[@]} ; do
    echo "command arg: $command"
  done
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  set -e
  main $@
fi

In simple terms, this script will only exec main if it's the script being called, similar to Python's if __name__ == '__main__' convention.

In the main function, I'm simply looping over all the command variables, but quote escaping isn't happening as expected:

$ tests/simple /bin/bash -c 'echo true'
command arg: /bin/bash
command arg: -c
command arg: echo
command arg: true

The last argument here should get parsed by Bash as a single argument, nevertheless it is split into individual words.

What am I doing wrong? I want echo true to show up as a single argument.

Upvotes: 1

Views: 252

Answers (3)

grail
grail

Reputation: 930

Quoting of @ passed to main was your issue, but I thought I would mention that you also do not need to assign the value inside main to use it. You could do the following:

main()
{
  for command
  do
    ...
  done
}

main "$@"

Upvotes: 0

codeforester
codeforester

Reputation: 42999

You are getting the right output except for the 'echo true' part which is getting word split. You need to use double quotes in your code:

main "$@"

And in the function:

function main() {
  local commands=("$@") # need () and double quotes here
  for command in "${commands[@]}" ; do
    echo "command arg: $command"
  done
}

The function gets its own copy of $@ and hence you don't really need to make a local copy of it.

With these changes, we get this output:

command arg: /bin/bash
command arg: -c
command arg: echo true

In general, it is not good to store shell commands in a variable. See BashFAQ/050.

See also:

Upvotes: 2

l'L'l
l'L'l

Reputation: 47169

You'll likely want to do something more like this:

function main() {
    while [ $# -gt 0 ]
    do
        echo "$1"
        shift
    done
}

main /bin/bash -c "echo true"

The key really being $#, which counts the number of command line arguments, (not including the invocation name $0). The environment variable $# is automatically set to the number of command line arguments. If the function/script was called with the following command line:

$ main /bin/bash -c "echo true"

$# would have the value "3" for the arguments: "/bin/bash", "-c", and "echo true". The last one counts as one argument, because they are enclosed within quotes.

  1. The shift command "shifts" all command line arguments one position to the left.
  2. The leftmost argument is lost (main).

Upvotes: 0

Related Questions