I have a list of arguments and three processes:

bash_script -> child -> grandchild

The list of arguments is aimed at the grandchild. I can modify all three processes. The grandfather script gets one argument for itself.

Is the following a proper way to pass the remaining arguments to the grandchild?

#!/usr/bin/env bash
# This is the grandfather    
first_arg="$1"
shift 1;

export MY_ARGS="$@"

I "spread" the env variable later, in the child process, as part of the command that calls the grandchild, something like:

grandchild --foo "$MY_ARGS"  # append $MY_ARGS as arguments to foo
  • I think this question is unclear. What are you trying to achieve? What's the input? What are your limitations? Why do you want to use env vars? – Tomasz Jun 19 at 18:54
  • 1
    Are you able to modify the grandchild process? Is there any requirement how the arguments should arrive there? – Tomasz Jun 19 at 19:00
  • 1
    And are you in control (able to edit) the intermediate process? – Tomasz Jun 19 at 19:01
  • 1
    If you control all the scripts/programs, then I don't see any issue with passing tho command line arguments as command line arguments between the stages of your processing. – Kusalananda Jun 19 at 19:13
  • 2
    If you're are starting process trees that are four or five or more levels deep, and you need to pass the initial command line arguments down to a leaf process, then you may want to start thinking about the organization of your project. – Kusalananda Jun 19 at 19:44
up vote 5 down vote accepted

In a script, you should not demote an array to a string. An environment variable and its value is a simple key=value pair where both key and value are strings. Demoting the positional parameters to a simple string (by concatenation) will make it difficult to retain separation between them, and it would be hard to get quoting right when you end up wanting to use them.

Instead, pass the positional parameters (command line argument) that you want to pass to the next script on its command line.

#!/bin/bash

first_arg=$1
shift

# later ...

./my_other_script "$@"

In the other script:

#!/bin/bash

# use "$@" here
foo --bar "$@"
  • I need to store it in an env var, because foo is a subprocess launched from another subprocess. – Alexander Mills Jun 19 at 18:30
  • do you think @mOdular's answer is ok? – Alexander Mills Jun 19 at 18:36
  • An environment variable is a simple key=value pair. If you demote the array to a simple string, then you lose the ability to distinguish between the original arguments, unless you explicitly delimit them in a way that you carefully arrange (and then parse that string). The best way is to never demote the elements to the bits of the same string and instead pass them on the command line as separate arguments. Exporting arrays may possible be working in bash (I honestly don't know), but it seems like a hack if anything. – Kusalananda Jun 19 at 18:40
  • As it stands, tbh this doesn't answer the question. I updated the OP to make it clearer. – Alexander Mills Jun 19 at 18:59

EDIT: it seems you can't export arrays in bash. You might need to export a function that sets up the array or something similar.

You should use an array, otherwise arguments with spaces will expand to multiple words.

export MY_ARGS=("$@")


foo --bar "${MY_ARGS[@]}"
  • thanks, probably more along the lines of what I am thinking – Alexander Mills Jun 19 at 18:31
  • hmmm, why not export MY_ARGS="${MY_ARGS[@]}"? – Alexander Mills Jun 19 at 18:35
  • Actually, I don't think this will work. See my edit. There are other questions on this site about this. – m0dular Jun 19 at 18:35
  • I think what I just suggested will work, above your last comment – Alexander Mills Jun 19 at 18:36

I am not sure why people are confused by the question, but maybe the question is confusing, here is what seems to work fine for me:

my_args_array=("$@")
export MY_ARGS="${my_args_array[@]}"

then we simply use MY_ARGS like so:

foo --bar $MY_ARGS

note as @Kusalananda points out: this will fail if the original arguments have whitespace in them. And in that case, it's best to pass arguments through child processes using $@, instead of an env variable.

  • 1
    Arrays don't export in Bash. (Or if they do, then not this way.) I think you're mixing together two different things, which makes you think you're right. – Tomasz Jun 19 at 18:52
  • 2
    Try that with set -- 'hello world' '1 2 3' 'abc'. You can't any longer distinguish between these three positional parameters in $MY_ARGS. – Kusalananda Jun 19 at 18:52
  • hmmm I agree that there might be a problem, but I don't exactly follow, what is the set command? – Alexander Mills Jun 19 at 18:57
  • 1
    The set command sets the positional parameters, $1, $2 etc. These are available as the array $@. – Kusalananda Jun 19 at 19:09
  • @Kusalananda ok I think I see what you are saying, so to fix this, could you do something like: export MY_ARGS="${my_args_array[@].join("'")}", that is, join surround argument in the array with single quotes? join is not the right function, but some sort of map. – Alexander Mills Jun 19 at 19:39

The general answer is this. You can pass the args using:

  • command line positional parameters
  • environment variables
  • any IPC (Inter-Process Communication) available on your OS

To focus on just the first two,

  • Positional parameters are the most natural way. This involves possible edition and further transmission of "$@", which stands for all currently defined positional parameters. This has to be done on all stages of the process hierarchy.
  • The environment variables are not as straight-forward as the positional parameters, as they come in a form of key-and-value pair. Even if you pass values this way, you need the keys to get them. As long as they're not the only environment variables, which I wouldn't assume without adequate consideration. But this method is viable. All you need to do is to share the keys among the interested parties. The keys themselves can be based on a pattern. Here's an example of how this can be done.

    There are two scripts -- p.bash and c.bash. p stands for a parent, while c is the child. They can be separated by intermediaries, as long as those don't remove the values transmitted through the environment. For simplicity, these two scripts have an immediate connection in that p.bash calls c.bash.

    Here's p.bash:

    #!/bin/bash
    shift #consume the first argument
    #parse and export the other arguments
    prefix=abcdef_
    i=0
    for a in "$@"; do
        export "$prefix""$i"="$a"
        ((i++))
    done
    ./c.bash
    

    And here's c.bash:

    #!/bin/bash
    #consume the env arguments
    prefix=abcdef_
    env | grep ^"$prefix"
    

    And here's the execution:

    $ ./p.bash arg "1 2" "3 4" "5 6"
    abcdef_0=1 2
    abcdef_1=3 4
    abcdef_2=5 6
    

    I'll explain two lines, one from each script.

    The first one is the generator,

     export "$prefix""$i"="$a"
    

    export populates the environment with variables of form abcdef_0, abcdef_1, etc. Now the child processes can find them in their environments. (As long as they're not unset by some intermediary.)

    env | grep ^"$prefix" in the child is the consumer. env lists the environment variables, and grep filters out all the lines not associated with the prefix responsible for the transmission in question. Once you understand what's going on here, you should be able to access the transmitted variables and make use of them inside the descendant process.

  • @ilkkachu Correct. I'll update my answer. – Tomasz Jun 19 at 21:06

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.