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
  • Why do you have to operate on the script arguments? Could you not use another array with a copy of them? – user147505 Dec 17 '19 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? – steeldriver Dec 17 '19 at 14:35
  • 1
    Call your function with the arguments of the script: myfunc "$@". – Kusalananda Dec 17 '19 at 14:44
  • @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 Dec 17 '19 at 14:46
  • @Kusalananda. It's OK to get the params, but not to set them. – Jacques Dec 17 '19 at 14:46
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
|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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