How do I remove positional parameters in bash?

79 Views Asked by At

I have a script that takes parameters and I want to remove given named parameters

e.g. script.sh a b c and I want to remove 'b' so that

    $1 = a
    $2 = c

This also must work for any given set of args such that e.g.

    script.sh "a 1" b "c 2"
    $1 = "a 1"
    $2 = "c 2"

A previous answer said

    for var in "$@"; do
        case $var in
            "a") echo -n $var ;;
            "b") ;;
        esac
    done

The generic version of the previous answer should have been (in order to remove b)

    for var in "$@"; do
        case $var in
            "b") ;;
            *) echo -n $var ;;
        esac
    done

But this fails for multiple parameters e.g.

    script.sh a b c
    Output: ac

We wanted a b and we wanted them in positional parameter $1=a and $2=b

So a better answer is needed.

4

There are 4 best solutions below

2
Mike Q On

I presented two options, a solution to your problem and in addition a more proper way of hanlding input arguments.

To Answer your question

Looking at this little bit of code, we exclude the argument options from the values by comparing each value to our arg_options array. Hence anything define in arg_options will not be in the final results.

The script loops over every input option, then runs another check (loop) to look for any matches in our arg options, if it's found we mark it as a value we don't want and we move on.

declare -a arg_options=( aaa bbb ccc ddd )

for each in "$@"; do
  found=0
  for opt in "${arg_options[@]}"; do
     if [ "$each" = "$opt" ]; then
        found=1
        break
     fi
  done
  if [ $found -ne 1 ]; then
          echo $each 
          # store to an array etc. here
  fi
done

To expand on your question

Take this example 'script.sh' with command line options:

Script.sh -mode active -count 10

Here's the code, and the nice part is that it will properly handle the inputs no matter what order they are passed to the script:

typeset mode='inactive'
typeset count

get_user_input_options() {
    while [[ $# > 0 ]] ;do
        key="$1"
        case ${key,,} in
            -m|--mode)
                mode="$2"
                shift
            ;;
            -c|--count)
                count="$2"
                shift
            ;;
            *)  echo "ERROR: Unknown option $key given."
                exit 1
            ;;
        esac
        shift
    done
}


get_user_input_options "$@" # handle user inputs (if any)

What are we doing in the script, we are defining a function to read in the options and set the global variables get_user_input_options. Look at how it is being called, it is taking in all of the command line inputs for handling with the "$@" option. Note: if you don't want it to be a script just remove the function line and it's closing bracket.

Then it is using a while loop to step over every option, but when it finds a match for an option it sets the value and shifts forward as that had the value of the option. It continues to step through the inputs until completed.

You will also notice that I set the global variable 'mode' to something so if the script is called without that option, it will be set to a default value.

0
Kaz On

In the special case (commonly occurring) that you want to remove some initial positional parameters, you use shift. By default that deletes $1; with a numeric argument, it will delete n parameters.

Now, in your specific case, we can use a combination of shift and set --.

local a=$1
shift 2  # delete $1 and $2
set -- "$a" "$@"   # insert $1 before original $3 ... $n

Using Bash arrays is another possibility. If we translate the "$@" arguments into a bash array.

local args=("$@")    # convert $1 $2 ... into ${args[0]} ${args[1]} ...
unset args[1]        # delete args[1]; note: elements DO NOT SHIFT DOWN!
set -- "${args[@]}"  # convert array back to positional params

When we convert the array back to positional params with the "${args[@]}" expansion syntax, the unset args[1] element is absent, and so the original $2 disappears.

Of course, if we need those arguments in order to pass them to a function or script, we don't have to convert the array back to positional parameters. Just:

local args=("$@")    # convert $1 $2 ... into ${args[0]} ${args[1]} 
unset args[1]        # delete second arg
script "${args[@]}"  # call script with second argument deleted

Likewise, in my very first example, we could just call whatever we need to call rather than using set to further edit the positionals:

local a=$1         # preserve $1 in temporary variable
shift 2            # delete $1 and $2
script "$a" "$@"   # call target script with original $1 $3 $4 ...
3
pmf On

As is tagged, you can use "${@:3}" to produce all arguments starting from the third. Use that in combination with $1 to reference all arguments but the second, and use set to reset the arguments accordingly:

#!/bin/bash

echo "BEFORE:"
for arg; do echo "=> [$arg]"; done

set -- "$1" "${@:3}"

echo "AFTER:"
for arg; do echo "=> [$arg]"; done

Example:

./script.sh "a 1" b "c 2"
BEFORE:
=> [a 1]
=> [b]
=> [c 2]
AFTER:
=> [a 1]
=> [c 2]
0
pjh On

Try this Shellcheck-clean Bash code:

#! /bin/bash -p

args=()
for a in "$@"; do
    [[ $a == b ]] || args+=( "$a" )
done
set -- "${args[@]}"

(( $# > 0 )) && printf '%s\n' "$@"
  • When run with arguments 'a 1' b 'c 2' it produces output

    a 1
    c 2
    
  • When run with arguments -x "a'1" b 'c 2' "'\$(echo REBOOTING >&2)'" it produces output

    -x
    a'1
    c 2
    '$(echo REBOOTING >&2)'
    
  • The -- argument to set prevents subsequent arguments (e.g. -x) being treated as options.