-->

Pass multi-word arguments to a bash function

2020-08-10 19:33发布

问题:

Inside a bash script function, I need to work with the command-line arguments of the script, and also with another list of arguments. So I'm trying to pass two argument lists to a function, the problem is that multi-word arguments get split.

function params()
{
    for PARAM in $1; do
        echo "$PARAM"
    done

    echo .

    for ITEM in $2; do
        echo "$ITEM"
    done
}

PARAMS="$@"
ITEMS="x y 'z t'"
params "$PARAMS" "$ITEMS"

calling the script gives me

myscript.sh a b 'c d'
a
b
c
d
.
x
y
'z
t'

Since there are two lists they must be passed as a whole to the function, the question is, how to iterate the elements while respecting multi-word items enclosed in single quotes 'c d' and 'z t'?

The workaround that I have (see below) makes use of BASH_ARGV so I need to pass just a single list to the function. However I would like to get a better understanding of what's going on and what's needed to make the above work.

function params()
{
    for PARAM in "${BASH_ARGV[@]}"; do
        echo "$PARAM"
    done

    echo .

    for ITEM in "$@"; do
        echo "$ITEM"
    done
}

params x y 'z t'

calling the script gives me

myscript.sh a b 'c d'
c d
b
a
.
x
y
z t

... Which is how I need it (except that first list is reversed, but that would be tolerable I guess)

回答1:

function params()
{
   arg=("$@")

   for ((i=1;i<=$1;i++)) ;do
       echo "${arg[i]}"
   done

   echo .

   for ((;i<=$#-$1+2;i++)) ;do
       echo "${arg[i]}"
   done
}

items=(w x 'z t')
params $# "$@" "${items[@]}"

Assuming you call your script with args a b 'c d', the output is:

a
b
c d
.
x
y
z t


回答2:

Peter.O's answer above works fine, and this is an addendum to it, with an example.

I needed a function or script that would take multi-word arguments, that would then be used to search the list of running processes and kill those that matched one of the arguments. The following script does that. The function kill_processes_that_match_arguments only needs one for loop, because my need was only to iterate over the set of all arguments to the function. Tested to work.

#!/bin/bash

function kill_processes_that_match_arguments()
{
    arg=("$@")
    unset PIDS_TO_BE_KILLED

    for ((i=0;i<$#;i++))
    do
        unset MORE_PIDS_TO_KILL
        MORE_PIDS_TO_KILL=$( ps gaux | grep "${arg[i]}" | grep -v 'grep' | awk '{ print $2 }' )
        if [[ $MORE_PIDS_TO_KILL ]]; then
            PIDS_TO_BE_KILLED="$MORE_PIDS_TO_KILL $PIDS_TO_BE_KILLED"
        fi
    done

    if [[ $PIDS_TO_BE_KILLED ]]; then
        kill -SIGKILL $PIDS_TO_BE_KILLED
        echo 'Killed processes:' $PIDS_TO_BE_KILLED
    else
        echo 'No processes were killed.'
    fi
}

KILL_THESE_PROCESSES=('a.single.word.argument' 'a multi-word argument' '/some/other/argument' 'yet another')
kill_processes_that_match_arguments "${KILL_THESE_PROCESSES[@]}"