3

Context:

I have an old bash script with a big section parsing its arguments. It happens now that I need to call this section twice, so I plan to move it to a function to avoid code duplication.

The problem:

In that section, set --, shift and $@ are used, meaning that they won't apply to the script anymore, but to the function, which is wrong.

Question:

From within the function, is there any way to get and set the script arguments ?

Scheme:

Something like this:

#!/bin/bash
# > 5000 lines

process_arg()
{
   # about 650 lines

   # set --
   # $@ $* $1 ...
   # shift <n>
}

while (( $# > 0 )); do
   case $1 in
      <cond>)
         <some code here>
         process_arg
         <some more code here>

      <other conditions and code here>

      *)
         <some different code here>
         process_arg
         <some different more code here>
   esac
   shift 1
done
13
  • 1
    Why do you have to operate on the script arguments? Could you not use another array with a copy of them?
    – user147505
    Commented Dec 17, 2019 at 14:34
  • At least the "get" half of your question is covered here: Is there a way to get the positional parameters of the script from inside a function in bash? Commented Dec 17, 2019 at 14:35
  • 1
    Call your function with the arguments of the script: myfunc "$@".
    – Kusalananda
    Commented Dec 17, 2019 at 14:44
  • 1
    @Tomasz. Because this script is old, is about 5000 lins long, and has at least more than 100 set --, $@ and shift combine. I would like better to avoid having to refactor it completely, if possible, or to do it the lightest way possible if I have too. You know: ideal world vs reality;
    – Jacques
    Commented Dec 17, 2019 at 14:46
  • @Kusalananda. It's OK to get the params, but not to set them.
    – Jacques
    Commented Dec 17, 2019 at 14:46

1 Answer 1

0

Disclaimer:

From the discussion above, I implemented a solution. This is not, by far, the one I dreamed of, because of the verbosity of ${args_array[1]}, compared to $1. Makes the source less readable. So improvements, or a better solution are still welcome.

Source:

Tested, something like this:

#!/bin/bash 

#########################    
# DEBUG
#########################    

# set -x
PS4='${xchars:-+} ${BASH_SOURCE}:${LINENO} (${FUNCNAME[@]}) + ' # full stack

#########################    
# INITIAL ARGS FOR TEST
#########################    
set -- a b c d e f g h

#########################    
# UTILITIES
#########################    

args_array=( "$@" ) # script args here

args_shift() # Usage readability OK, replaces shift <n>
{
   typeset n=${1:-1}

               echo "args_shift $1 in ${FUNCNAME[1]} -- ${args_array[@]}"

   args_array=( "${args_array[@]:$n}" ) # ${1:-1} unsupported in this context

               echo "args_shift $1 in ${FUNCNAME[1]} ++ ${args_array[@]}"
}

args_set() # Usage readability OK, replaces set -- <args>
{
               echo "args_set $@ in ${FUNCNAME[1]} -- ${args_array[@]}"

   args_array=( "$@" ) # function args here

               echo "args_set $@ in ${FUNCNAME[1]} ++ ${args_array[@]}"
}

# Usage
# search/replace OK, and good readability afterward
# shift <n> ---> args_shift <n>
# set -- <args> ---> args_set <args>

# search/replace OK, but bad readability afterward, and refactoring--
# $@ ---> ${args_array[@]}
# $# ---> ${#args_array[@]}
# $1 ---> ${args_array[0]}   !!! 1 -> 0
# $2 ---> ${args_array[1]}   !!! 2 -> 1
# etc

#########################
# TEST
#########################    

f()
{
   args_shift
}

g()
{
   args_set A B C D
}

# main

echo "main -- ${args_array[@]}"
f
args_shift 2
f
g
args_shift
f
echo "main ++ ${args_array[@]}"

Output:

main -- a b c d e f g h
args_shift  in f -- a b c d e f g h
args_shift  in f ++ b c d e f g h
args_shift 2 in main -- b c d e f g h
args_shift 2 in main ++ d e f g h
args_shift  in f -- d e f g h
args_shift  in f ++ e f g h
args_set A B C D in g -- e f g h
args_set A B C D in g ++ A B C D
args_shift  in main -- A B C D
args_shift  in main ++ B C D
args_shift  in f -- B C D
args_shift  in f ++ C D
main ++ C D

Remarks:

  1. Works but not the most readable solution, and refactoring not so light, because there are several forms of usage to take into consideration: $1, more or less ${1[:/][^}]} or ${!1[:/][^}]} etc, while avoiding those in function, awk, perl etc.
  2. For some, as variable names are case sensitive in bash and, I think, more or less seldom used, on could use A or _A instead of args_array but, to my taste, ${A[1]} or so is even less readable in a long source than ${args_array[1]}.

My situation:

There are at least 616 occurrences to take care of... carefully (some are in functions, awk or perl scripts etc)

for s in shift 'set --' '$@' '${@' '$*' '${*' '$1' '${1' '$2' '${2' '$3' '${3' '$4' '${4' '$5' '${5'; do
   printf '%-10s: %d\n' "$s " "$(fgrep $s <script>|wc -l)"
done # |awk '{sum+=$NF};END{print sum}'

shift     : 44
set --    : 189
$@        : 39
${@       : 2
$*        : 7
${*       : 0
$1        : 182
${1       : 79
$2        : 48
${2       : 3
$3        : 15
${3       : 0
$4        : 8
${4       : 0
$5        : 0
${5       : 0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .