Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
scripting:posparams [2011/06/20 19:37]
thebonsai [Filter unwanted options with a wrapper script]
scripting:posparams [2018/05/12 18:04] (current)
wayeoyuz Typo
Line 6: Line 6:
 ===== Intro ===== ===== Intro =====
  
-The day will come when you want to give arguments to your scripts. These arguments are reflected ​as the **positional parameters** ​inside your scriptMost relevant special parameters are described below:+The day will come when you want to give arguments to your scripts. These arguments are known as **positional parameters**. ​Some relevant special parameters are described below:
 ^Parameter(s)^Description^ ^Parameter(s)^Description^
 |''​$0''​|the first positional parameter, equivalent to ''​argv[0]''​ in C, see [[scripting:​posparams#​the_first_argument | the first argument]]| |''​$0''​|the first positional parameter, equivalent to ''​argv[0]''​ in C, see [[scripting:​posparams#​the_first_argument | the first argument]]|
Line 17: Line 17:
  
  
-These positional parameters reflect exactly what was given to the script when it was called. ​There are no special things interpreted: ​Option-switch parsing (''​-h''​ for displaying help) is not done in this stage.+These positional parameters reflect exactly what was given to the 
 +script when it was called. 
 + 
 +Option-switch parsing (e.g. ''​-h''​ for displaying help) is not performed at 
 +this point.
  
 See also [[dict:​terms:​parameter | the dictionary entry for "​parameter"​]]. See also [[dict:​terms:​parameter | the dictionary entry for "​parameter"​]].
Line 24: Line 28:
 ===== The first argument ===== ===== The first argument =====
  
-The very first argument you can access is referenced ​by ''​$0''​. It usually ​is set to the script'​s name exactly ​like it was called, and it's set on shell initialization:​+The very first argument you can access is referenced ​as ''​$0''​. It 
 +is usually set to the script'​s name exactly ​as called, and it's 
 +set on shell initialization:​
  
 __Testscript__ - it just echos ''​$0'':​ __Testscript__ - it just echos ''​$0'':​
Line 31: Line 37:
 echo "​$0"​ echo "​$0"​
 </​code>​ </​code>​
-You see, ''​$0''​ is always set to however you call that script (''​$''​ is the prompt...):+You see, ''​$0''​ is always set to the name the script ​is called with (''​>''​ is the prompt...):
 <​code>​ <​code>​
 > ./​testscript ​ > ./​testscript ​
Line 47: Line 53:
 </​code>​ </​code>​
  
 +In other terms, ''​$0''​ is not a positional parameter, it's a
 +special parameter independent from the positional parameter list. It
 +can be set to anything. In the **ideal** case it's the pathname
 +of the script, but since this gets set on invocation, the invoking
 +program can easily influence it (the ''​login''​ program does that for
 +login shells, by prefixing a dash, for example).
  
-Also, to be over-exact: ''​$0''​ is not a positional parameter, it's a special parameter independent from the real parameter list. Also it really can be set to anything. In the **ideal** case it's the pathname of the script, but since this is set on invocation, the invoking program can easily influence it (the ''​login''​ program does that for login shells, by prepending a dash, for example). +Inside a function, ''​$0''​ still behaves as described ​above. To 
- +get the function name, use ''​$FUNCNAME''​.
-Inside a function, ''​$o''​ still reflects what was told above. To get the function name, use ''​$FUNCNAME''​. +
  
 ===== Shifting ===== ===== Shifting =====
Line 62: Line 72:
   * in general: ''​$N''​ will become ''​$N-1''​   * in general: ''​$N''​ will become ''​$N-1''​
  
-The command can take a number as argument: ​How many positions to shift. +The command can take a number as argument: ​Number of positions to shift. 
- So, a ''​shift 4'' ​will shift ''​$5''​ to ''​$1''​.+ e.g. ''​shift 4'' ​shifts ​''​$5''​ to ''​$1''​.
  
 ===== Using them ===== ===== Using them =====
Line 81: Line 91:
 echo "​Argument 5: $5" echo "​Argument 5: $5"
 </​code>​ </​code>​
-Well, it might be useful in one or the other situation, ​but this way is not very flexibleYou're fixed in your maximum number of arguments - which is a bad idea if you write a script that takes many filenames as arguments.+ 
 +While useful in another ​situation, this way is lacks flexibility. 
 +The maximum number of arguments ​is a fixedvalue 
 +- which is a bad idea if you write a script that takes many filenames 
 +as arguments.
  
 => forget that one => forget that one
  
-==== Loopings ​====+==== Loops ====
  
 There are several ways to loop through the positional parameters. There are several ways to loop through the positional parameters.
Line 91: Line 105:
 ---- ----
  
-You can code a [[syntax:​ccmd:​c_for | C-style for-loop]] using ''​$#''​ as end-value. On every iteration, the ''​shift''​-command is used to shift the argument list:+You can code a [[syntax:​ccmd:​c_for | C-style for-loop]] using ''​$#''​ 
 +as the end value. On every iteration, the ''​shift''​-command is used to 
 +shift the argument list: 
 <​code>​ <​code>​
 numargs=$# numargs=$#
-for ((i=1 ; i <= numargs ; i++))do +for ((i=1 ; i <= numargs ; i++)) 
-  echo "​$1"​ +do 
-  shift+    echo "​$1"​ 
 +    shift
 done done
 </​code>​ </​code>​
-Not very stylish, but okay, usable. The ''​numargs''​ variable is used to store the initial value of ''​$#''​ because ​it will change ​due to the shifting.+ 
 +Not very stylish, but usable. The ''​numargs''​ variable is used 
 +to store the initial value of ''​$#''​ because ​the shift command 
 +will change ​it as the script runs.
  
 ---- ----
  
-Another way to iterate one-by-one ​is the ''​for''​-loop without given wordlist, it will use the positional parameters as wordlist ​then:+Another way to iterate one argument at a time is the ''​for''​ loop 
 +without ​given wordlist. The loop uses the positional parameters as wordlist: 
 <​code>​ <​code>​
-for argdo +for arg 
-  echo "​$arg"​+do 
 +    echo "​$arg"​
 done done
 </​code>​ </​code>​
-__Advantage:​__ The positional parameters will be preserved ​and not shifted into nirvana!+__Advantage:​__ The positional parameters will be preserved
  
 ---- ----
  
-The next way is similar to the first example (the ''​for''​-loop), but it doesn'​t test for reaching ''​$#''​. It shifts and checks if ''​$1''​ still expands to something, using the [[commands:​classictest | test command]]:+The next method ​is similar to the first example (the ''​for''​ loop), but 
 +it doesn'​t test for reaching ''​$#''​. It shifts and checks if ''​$1''​ 
 +still expands to something, using the [[commands:​classictest | test command]]: 
 <​code>​ <​code>​
-while [ "​$1"​ ]do +while [ "​$1"​ ] 
-  echo "​$1"​ +do 
-  shift+    echo "​$1"​ 
 +    shift
 done done
 </​code>​ </​code>​
-Looks nice, but it has the disadvantage ​to stop when ''​$1''​ is empty (null-string). Let's modify it to run as long as ''​$1''​ is defined (but may be empty), using [[syntax:​pe#​use_an_alternate_value | parameter expansion for an alternate value]]:+ 
 +Looks nice, but has the disadvantage ​of stopping ​when ''​$1''​ is empty 
 +(null-string). Let's modify it to run as long as ''​$1''​ is defined 
 +(but may be null), using [[syntax:​pe#​use_an_alternate_value | parameter expansion for an alternate value]]: 
 <​code>​ <​code>​
 while [ "​${1+defined}"​ ]; do while [ "​${1+defined}"​ ]; do
Line 127: Line 159:
 done done
 </​code>​ </​code>​
- 
- 
  
 ==== Getopts ==== ==== Getopts ====
Line 138: Line 168:
 ==== All Positional Parameters ==== ==== All Positional Parameters ====
  
-Sometimes it's necessary to just "​relay"​ or "hand through" given arguments to another program. It's very inefficient to do that in one of these loops, ​also you will destroy integrity, most likely (spaces!).+Sometimes it's necessary to just "​relay"​ or "pass" given 
 +arguments to another program. It's very inefficient to do that in one 
 +of these loops, ​as you will destroy integrity, most likely 
 +(spaces!).
  
-The shell-developers ​invented ​''​$*''​ and ''​$@''​ for this purpose.+The shell developers ​created ​''​$*''​ and ''​$@''​ for this purpose.
  
-As overwiew:+As overview:
  
 ^Syntax ​     ^Effective result ​                ^ ^Syntax ​     ^Effective result ​                ^
Line 150: Line 183:
 |  ''"​$@"'' ​ |  ''"​$1"​ "​$2"​ "​$3"​ ... "​${N}"'' ​ | |  ''"​$@"'' ​ |  ''"​$1"​ "​$2"​ "​$3"​ ... "​${N}"'' ​ |
  
-You see that without ​being quoted (double-quoted), both have the same effect: All positional parameters from ''​$1''​ to the last used one are expanded without any specials. A subsequent wordsplitting will recognize as much words as expanded before (i.e. it "​doesn'​t preserve words"​).+Without ​being quoted (double ​quotes), both have the same 
 +effect: All positional parameters from ''​$1''​ to the last one used are 
 +expanded without any special handling.
  
-When the ''​$*''​ special parameter is doublequoted, it expands to the equivalent of: ''"​$1c$2c$3c$4c........$N"'',​ where '​c'​ is the first character of ''​IFS''​.+When the ''​$*''​ special parameter is double quoted, it expands to the 
 +equivalent of: ''"​$1c$2c$3c$4c........$N"'',​ where '​c'​ is the first 
 +character of ''​IFS''​.
  
-But when the ''​$@''​ special parameter is used inside ​doublequotes, it expands to the equivanent of...+But when the ''​$@''​ special parameter is used inside ​double quotes, it 
 +expands to the equivanent of...
  
 ''"​$1"​ "​$2"​ "​$3"​ "​$4"​ ..... "​$N"''​ ''"​$1"​ "​$2"​ "​$3"​ "​$4"​ ..... "​$N"''​
  
-...which **exactly ​reflects all positional parameters ​like they were initially ​set** and given to the script or the function. If you want to re-use your positional parameters to **call another program** (for example in a wrapper-script),​ then this is the choice for you, use the doublequoted ​''"​$@"''​.+...which **reflects all positional parameters ​as they were 
 +set initially** and passed ​to the script or function. If you want 
 +to re-use your positional parameters to **call another program** (for 
 +example in a wrapper-script),​ then this is the choice for you, use 
 +double quoted ​''"​$@"''​.
  
 Well, let's just say: **You almost always want a quoted ''"​$@"''​!** Well, let's just say: **You almost always want a quoted ''"​$@"''​!**
Line 164: Line 206:
 ==== Range Of Positional Parameters ==== ==== Range Of Positional Parameters ====
  
-Another way to mass-expand the positional parameters is similar to what is possible for a range of characters using the [[syntax:​pe#​substring_expansion | substring expansion]] on normal parameters and the range mass expansion of [[syntax:​arrays | arrays]].+Another way to mass expand the positional parameters is similar to 
 +what is possible for a range of characters using 
 +[[syntax:​pe#​substring_expansion | substring expansion]] on normal 
 +parameters and the mass expansion ​range of [[syntax:​arrays | arrays]].
  
 ''​${@:​START:​COUNT}''​ ''​${@:​START:​COUNT}''​
Line 174: Line 219:
 ''"​${*:​START:​COUNT}"''​ ''"​${*:​START:​COUNT}"''​
  
-The rules for using ''​@''​ or ''​*''​ and the quoting are the same as above. This will expand ''​COUNT''​ number of positional parameters ​starting ​at ''​START''​. ''​COUNT''​ can be omitted (''​${@:​START}''​),​ in this case all positional parameters beginning at ''​START''​ are expanded.+The rules for using ''​@''​ or ''​*''​ and quoting are the same as 
 +above. This will expand ''​COUNT''​ number of positional parameters 
 +beginning ​at ''​START''​. ''​COUNT''​ can be omitted (''​${@:​START}''​),​ in 
 +which caseall positional parameters beginning at ''​START''​ are 
 +expanded.
  
-If ''​START''​ is negative, the positional parameters are numbered ​from the last one backwards.+If ''​START''​ is negative, the positional parameters are numbered ​in reverse 
 +starting with the last one.
  
-''​COUNT''​ may not be negative, ​so elements are always counted in the forward direction.+''​COUNT''​ may not be negative, ​i.e. the element count may not be decremented.
  
 __**Example:​**__ __**Example:​**__
Line 186: Line 236:
 </​code>​ </​code>​
  
-:V4: __**Attention**__: ​Since Bash 4, a ''​START''​ of ''​0''​ includes the special parameter ''​$0'',​ i.e. the shell name or whatever ​it'​s ​set to, when the positional parameters are in use. A ''​START''​ of ''​1''​ begins at ''​$1''​. In Bash 3 and older, both ''​0''​ and ''​1''​ began at ''​$1''​.+__**Attention**__: ​As of Bash 4, a ''​START''​ of ''​0''​ includes the special parameter ''​$0'',​ i.e. the shell name or whatever ​$0 is set to, when the positional parameters are in use. A ''​START''​ of ''​1''​ begins at ''​$1''​. In Bash 3 and older, both ''​0''​ and ''​1''​ began at ''​$1''​.
  
 ===== Setting Positional Parameters ===== ===== Setting Positional Parameters =====
  
-Letting the caller set the positional parameters, ​by giving parameters on commandline, ​is not the only way to set them. +Setting ​positional parameters ​with command line arguments, 
-The [[ commands:​builtin:​set | set builtin command ]] can be used to "​artificially"​ change the positional parameters from inside the script or function:+is not the only way to set them. 
 +The [[ commands:​builtin:​set | builtin command, set ]] 
 +may be used to "​artificially"​ change the positional parameters from 
 +inside the script or function:
  
 <​code>​ <​code>​
Line 205: Line 258:
 </​code>​ </​code>​
  
-It's wise to signal "end of options"​ when setting positional parameters this way. If not, dashes might be interpreted as option ​tag by ''​set''​ itself:+It's wise to signal "end of options"​ when setting positional 
 +parameters this way. If not, the dashes might be interpreted as an option ​switch 
 +by ''​set''​ itself: 
 <​code>​ <​code>​
 # both ways work, but behave differently. See the article about the set command! # both ways work, but behave differently. See the article about the set command!
 set -- ... set -- ...
 set - ... set - ...
 +</​code>​
 +
 +Alternately this will also preserve any verbose (-v) or tracing (-x) flags, which may otherwise be reset by ''​set''​
 +<​code>​
 +set -$- ...
 </​code>​ </​code>​
  
Line 217: Line 278:
  
 ==== Using a while loop ==== ==== Using a while loop ====
-This one uses a [[syntax:​ccmd:​while_loop | while loop]] to switch from argument to argument, and a [[syntax:​ccmd:​case | case statement]] to interpret these arguments. 
-<​code>​ 
-while [ "​${1+isset}"​ ]; do 
-  case "​$1"​ in 
-    -x|--extmode) 
-      extendedmode=1 
-      shift 
-      ;; 
-    -u|--user) 
-      username="​$2"​ 
-      shift 2 
-      ;; 
-    -f|--configfile) 
-      config="​$2"​ 
-      shift 2 
-      ;; 
-    -h|--help) 
-      display_help # a function ;-) 
-      # no shifting needed here, we'll quit! 
-      exit 
-      ;; 
-    *) 
-      echo "​Error:​ Unknown option: $1" >&2 
-      exit 1 
-      ;; 
-  esac 
-done 
-</​code>​ 
  
-It`s not that flexible, it doesn'​t auto-interpret mixed options, and some other things, but it works and is a good rudimentary way to parse your arguments.+To make your program accept options as standard command syntax:
  
 +''​COMMAND [options] <​params>'' ​ # Like 'cat -A file.txt'​
  
-==== Tuned while loop ====+See simple option parsing code below. It's not that flexible. It 
 +doesn'​t auto-interpret combined options (-fu USER) but it works and is 
 +a good rudimentary way to parse your arguments.
  
-Basically ​it's the very same as the example above, but this one only reacts on arguments matching ''​-*''​ (beginning with a dash), otherwise it quitsThat way you can provide the parsing of the recommended UNIX(r)-argument-way:​+<​code>​ 
 +#!/bin/sh 
 +# Keeping options in alphabetical order makes it easy to add more.
  
-''​COMMAND <​options>​ <​mass-options>''​ +while : 
- +do 
-like +    case "​$1"​ in 
- +      -| --file
-''​cat -A *''​ +   file="​$2" ​  # You may want to check validity of $2 
- +   ​shift 
-where ''​*''​ might expand to thousands of filenames. +   ;; 
- +      ​-h | --help) 
-<​code>​ +   display_help ​ # Call your function 
-while [[ $1 = -* ]]; do +   # no shifting needed here, we're done. 
-  case "​$1"​ in +   exit 0 
-    -x|--extmode+   ​;; 
-      ​extendedmode=1 +      -u | --user) 
-      shift +   ​username="​$2" ​# You may want to check validity of $2 
-      ;; +   ​shift 2 
-    -u|--user) +   ​;; 
-      username="​$2"​ +      -| --verbose
-      shift 2 +          # ​ It's better to assign a string, than a number like "​verbose=1" 
-      ;; +   #  because if you're debugging the script with "bash -x" code like this: 
-    -f|--configfile+   # 
-      ​config="$2+   #    if [ "$verbose" ​] ... 
-      shift 2 +   # 
-      ;; +   #  You will see: 
-    -h|--help) +   # 
-      display_help ​a function ;-) +   #    if [ "​verbose"​ ] ... 
-      # no shifting needed here, we'll quit! +   # 
-      exit +          #  Instead of cryptic 
-      ​;; +   # 
-    ​*) +   #    if [ "​1"​ ] ... 
-      echo "​Error:​ Unknown option: $1" >&​2 +   # 
-      exit 1 +   verbose="​verbose"​ 
-      ;; +   ​shift 
-  esac+   ​;; 
 +      --) # End of all options 
 +   shift 
 +   break; 
 +      ​-*) 
 +   ​echo "​Error:​ Unknown option: $1" >&​2 
 +   ​exit 1 
 +   ​;; 
 +      ​*) ​ # No more options 
 +   break 
 +   ;; 
 +    ​esac
 done done
  
-echo "​Processing files..."​ +# End of file
- +
-for file; do +
-  echo "File: $file"​ +
-  # ... +
-done+
 </​code>​ </​code>​
  
-Due to the shifting in the while-loop, the mass-arguments begin at ''​$1'',​ they'​re fully accessible by ''"​$@"''​ or ''​for i''​.+==== Filter unwanted options with a wrapper script ====
  
-Note the ''​while''​-condition: It's not the test command, ​it'the [[syntax:​ccmd:​conditional_expression | conditional expression]],​ since we do [[syntax:​pattern | pattern matching]].+This simple wrapper enables filtering unwanted options (here: ​''​-a''​ 
 +and ''​--all''​ for ''​ls'​') out of the command ​line. It reads the 
 +positional parameters and builds a filtered array consisting of themthen 
 +calls ''​ls''​ with the new option setIt also respects the ''​--''​ 
 +as "end of options"​ for ''​ls''​ and doesn'​t change anything after it:
  
- 
- 
- 
-==== Filter unwanted options with a wrapper script ==== 
-This simple wrapper allows to filter unwanted options (here: ''​-a''​ and ''​--all''​ for ''​ls''​) out of the commandline. It reads the positional parameters and builds a (filtered) array out of them, then it calls ''​ls''​ with the new option set. It also respects the ''​--''​ as "end of options"​ for ''​ls''​ and doesn'​t change anything after it: 
 <​code>​ <​code>​
 #!/bin/bash #!/bin/bash
Line 313: Line 356:
 eoo=0       # end of options reached eoo=0       # end of options reached
  
-while [[ $1 ]]do +while [[ $1 ]] 
-if ! ((eoo)); then +do 
-  case "​$1"​ in +    if ! ((eoo)); then 
-    -[^-]*a*|-a?​*) + case "​$1"​ in 
-      options+=("​${1//​a}"​) +   -a) 
-      shift +       shift 
-      ;; +       ;; 
-    -a) +   --all) 
-      shift +       shift 
-      ;; +       ;; 
-    --all) +   ​-[^-]*a*|-a?​*) 
-      shift +       ​options+=("​${1//​a}"​) 
-      ;; +       ​shift 
-    ​--) +       ​;; 
-      eoo=1 +   ​--) 
-      options+=("​$1"​) +       ​eoo=1 
-      shift +       ​options+=("​$1"​) 
-      ;; +       ​shift 
-    *) +       ​;; 
-      options+=("​$1"​) +   ​*) 
-      shift +       ​options+=("​$1"​) 
-      ;; +       ​shift 
-  esac +       ​;; 
-else + esac 
-  options+=("​$1"​) +    else 
-  # Another (worse) way of doing the same thing: + options+=("​$1"​) 
-  # options=("​${options[@]}"​ "​$1"​) + 
-  shift + # Another (worse) way of doing the same thing: 
-fi+ # options=("​${options[@]}"​ "​$1"​) 
 + shift 
 +    fi
 done done
  
 /bin/ls "​${options[@]}"​ /bin/ls "​${options[@]}"​
 </​code>​ </​code>​
- 
- 
  
 ==== Using getopts ==== ==== Using getopts ====
  
 There is a [[howto:​getopts_tutorial|small tutorial dedicated to ''​getopts''​]] (//under construction//​). There is a [[howto:​getopts_tutorial|small tutorial dedicated to ''​getopts''​]] (//under construction//​).
- 
- 
  
 ===== See also ===== ===== See also =====
  • scripting/posparams.1308598623.txt
  • Last modified: 2011/06/20 19:37
  • by thebonsai