#!/bin/bash # basics-reviewed.bash # File extension == *.bash == specific to Bash # Copyright (c) Michael S. Zick, 2003; All rights reserved. # License: Use in any form, for any purpose. # Revision: $ID$ # # Edited for layout by M.C. # (author of the "Advanced Bash Scripting Guide") # This script tested under Bash versions 2.04, 2.05a and 2.05b. # It may not work with earlier versions. # This demonstration script generates one --intentional-- #+ "command not found" error message. See line 394. # The current Bash maintainer, Chet Ramey, has fixed the items noted #+ for an upcoming version of Bash. ###-------------------------------------------### ### Pipe the output of this script to 'more' ### ###+ else it will scroll off the page. ### ### ### ### You may also redirect its output ### ###+ to a file for examination. ### ###-------------------------------------------### # Most of the following points are described at length in #+ the text of the foregoing "Advanced Bash Scripting Guide." # This demonstration script is mostly just a reorganized presentation. # -- msz # Variables are not typed unless otherwise specified. # Variables are named. Names must contain a non-digit. # File descriptor names (as in, for example: 2>&1) #+ contain ONLY digits. # Parameters and Bash array elements are numbered. # (Parameters are very similar to Bash arrays.) # A variable name may be undefined (null reference). unset VarNull # A variable name may be defined but empty (null contents). VarEmpty='' # Two, adjacent, single quotes. # A variable name my be defined and non-empty VarSomething='Literal' # A variable may contain: # * A whole number as a signed 32-bit (or larger) integer # * A string # A variable may also be an array. # A string may contain embedded blanks and may be treated #+ as if it where a function name with optional arguments. # The names of variables and the names of functions #+ are in different namespaces. # A variable may be defined as a Bash array either explicitly or #+ implicitly by the syntax of the assignment statement. # Explicit: declare -a ArrayVar # The echo command is a built-in. echo $VarSomething # The printf command is a built-in. # Translate %s as: String-Format printf %s $VarSomething # No linebreak specified, none output. echo # Default, only linebreak output. # The Bash parser word breaks on whitespace. # Whitespace, or the lack of it is significant. # (This holds true in general; there are, of course, exceptions.) # Translate the DOLLAR_SIGN character as: Content-Of. # Extended-Syntax way of writing Content-Of: echo ${VarSomething} # The ${ ... } Extended-Syntax allows more than just the variable #+ name to be specified. # In general, $VarSomething can always be written as: ${VarSomething}. # Call this script with arguments to see the following in action. # Outside of double-quotes, the special characters @ and * #+ specify identical behavior. # May be pronounced as: All-Elements-Of. # Without specification of a name, they refer to the #+ pre-defined parameter Bash-Array. # Glob-Pattern references echo $* # All parameters to script or function echo ${*} # Same # Bash disables filename expansion for Glob-Patterns. # Only character matching is active. # All-Elements-Of references echo $@ # Same as above echo ${@} # Same as above # Within double-quotes, the behavior of Glob-Pattern references #+ depends on the setting of IFS (Input Field Separator). # Within double-quotes, All-Elements-Of references behave the same. # Specifying only the name of a variable holding a string refers #+ to all elements (characters) of a string. # To specify an element (character) of a string, #+ the Extended-Syntax reference notation (see below) MAY be used. # Specifying only the name of a Bash array references #+ the subscript zero element, #+ NOT the FIRST DEFINED nor the FIRST WITH CONTENTS element. # Additional qualification is needed to reference other elements, #+ which means that the reference MUST be written in Extended-Syntax. # The general form is: ${name[subscript]}. # The string forms may also be used: ${name:subscript} #+ for Bash-Arrays when referencing the subscript zero element. # Bash-Arrays are implemented internally as linked lists, #+ not as a fixed area of storage as in some programming languages. # Characteristics of Bash arrays (Bash-Arrays): # -------------------------------------------- # If not otherwise specified, Bash-Array subscripts begin with #+ subscript number zero. Literally: [0] # This is called zero-based indexing. ### # If not otherwise specified, Bash-Arrays are subscript packed #+ (sequential subscripts without subscript gaps). ### # Negative subscripts are not allowed. ### # Elements of a Bash-Array need not all be of the same type. ### # Elements of a Bash-Array may be undefined (null reference). # That is, a Bash-Array my be "subscript sparse." ### # Elements of a Bash-Array may be defined and empty (null contents). ### # Elements of a Bash-Array may contain: # * A whole number as a signed 32-bit (or larger) integer # * A string # * A string formated so that it appears to be a function name # + with optional arguments ### # Defined elements of a Bash-Array may be undefined (unset). # That is, a subscript packed Bash-Array may be changed # + into a subscript sparse Bash-Array. ### # Elements may be added to a Bash-Array by defining an element #+ not previously defined. ### # For these reasons, I have been calling them "Bash-Arrays". # I'll return to the generic term "array" from now on. # -- msz # Demo time -- initialize the previously declared ArrayVar as a #+ sparse array. # (The 'unset ... ' is just documentation here.) unset ArrayVar[0] # Just for the record ArrayVar[1]=one # Unquoted literal ArrayVar[2]='' # Defined, and empty unset ArrayVar[3] # Just for the record ArrayVar[4]='four' # Quoted literal # Translate the %q format as: Quoted-Respecting-IFS-Rules. echo echo '- - Outside of double-quotes - -' ### printf %q ${ArrayVar[*]} # Glob-Pattern All-Elements-Of echo echo 'echo command:'${ArrayVar[*]} ### printf %q ${ArrayVar[@]} # All-Elements-Of echo echo 'echo command:'${ArrayVar[@]} # The use of double-quotes may be translated as: Enable-Substitution. # There are five cases recognized for the IFS setting. echo echo '- - Within double-quotes - Default IFS of space-tab-newline - -' IFS=$'\x20'$'\x09'$'\x0A' # These three bytes, #+ in exactly this order. printf %q "${ArrayVar[*]}" # Glob-Pattern All-Elements-Of echo echo 'echo command:'"${ArrayVar[*]}" ### printf %q "${ArrayVar[@]}" # All-Elements-Of echo echo 'echo command:'"${ArrayVar[@]}" echo echo '- - Within double-quotes - First character of IFS is ^ - -' # Any printing, non-whitespace character should do the same. IFS='^'$IFS # ^ + space tab newline ### printf %q "${ArrayVar[*]}" # Glob-Pattern All-Elements-Of echo echo 'echo command:'"${ArrayVar[*]}" ### printf %q "${ArrayVar[@]}" # All-Elements-Of echo echo 'echo command:'"${ArrayVar[@]}" echo echo '- - Within double-quotes - Without whitespace in IFS - -' IFS='^:%!' ### printf %q "${ArrayVar[*]}" # Glob-Pattern All-Elements-Of echo echo 'echo command:'"${ArrayVar[*]}" ### printf %q "${ArrayVar[@]}" # All-Elements-Of echo echo 'echo command:'"${ArrayVar[@]}" echo echo '- - Within double-quotes - IFS set and empty - -' IFS='' ### printf %q "${ArrayVar[*]}" # Glob-Pattern All-Elements-Of echo echo 'echo command:'"${ArrayVar[*]}" ### printf %q "${ArrayVar[@]}" # All-Elements-Of echo echo 'echo command:'"${ArrayVar[@]}" echo echo '- - Within double-quotes - IFS undefined - -' unset IFS ### printf %q "${ArrayVar[*]}" # Glob-Pattern All-Elements-Of echo echo 'echo command:'"${ArrayVar[*]}" ### printf %q "${ArrayVar[@]}" # All-Elements-Of echo echo 'echo command:'"${ArrayVar[@]}" # Put IFS back to the default. # Default is exactly these three bytes. IFS=$'\x20'$'\x09'$'\x0A' # In exactly this order. # Interpretation of the above outputs: # A Glob-Pattern is I/O; the setting of IFS matters. ### # An All-Elements-Of does not consider IFS settings. ### # Note the different output using the echo command and the #+ quoted format operator of the printf command. # Recall: # Parameters are similar to arrays and have the similar behaviors. ### # The above examples demonstrate the possible variations. # To retain the shape of a sparse array, additional script #+ programming is required. ### # The source code of Bash has a routine to output the #+ [subscript]=value array assignment format. # As of version 2.05b, that routine is not used, #+ but that might change in future releases. # The length of a string, measured in non-null elements (characters): echo echo '- - Non-quoted references - -' echo 'Non-Null character count: '${#VarSomething}' characters.' # test='Lit'$'\x00''eral' # $'\x00' is a null character. # echo ${#test} # See that? # The length of an array, measured in defined elements, #+ including null content elements. echo echo 'Defined content count: '${#ArrayVar[@]}' elements.' # That is NOT the maximum subscript (4). # That is NOT the range of the subscripts (1 . . 4 inclusive). # It IS the length of the linked list. ### # Both the maximum subscript and the range of the subscripts may #+ be found with additional script programming. # The length of a string, measured in non-null elements (characters): echo echo '- - Quoted, Glob-Pattern references - -' echo 'Non-Null character count: '"${#VarSomething}"' characters.' # The length of an array, measured in defined elements, #+ including null-content elements. echo echo 'Defined element count: '"${#ArrayVar[*]}"' elements.' # Interpretation: Substitution does not effect the ${# ... } operation. # Suggestion: # Always use the All-Elements-Of character #+ if that is what is intended (independence from IFS). # Define a simple function. # I include an underscore in the name #+ to make it distinctive in the examples below. ### # Bash separates variable names and function names #+ in different namespaces. # The Mark-One eyeball isn't that advanced. ### _simple() { echo -n 'SimpleFunc'$@ # Newlines are swallowed in } #+ result returned in any case. # The ( ... ) notation invokes a command or function. # The $( ... ) notation is pronounced: Result-Of. # Invoke the function _simple echo echo '- - Output of function _simple - -' _simple # Try passing arguments. echo # or (_simple) # Try passing arguments. echo echo '- Is there a variable of that name? -' echo $_simple not defined # No variable by that name. # Invoke the result of function _simple (Error msg intended) ### $(_simple) # Gives an error message: # line 394: SimpleFunc: command not found # --------------------------------------- echo ### # The first word of the result of function _simple #+ is neither a valid Bash command nor the name of a defined function. ### # This demonstrates that the output of _simple is subject to evaluation. ### # Interpretation: # A function can be used to generate in-line Bash commands. # A simple function where the first word of result IS a bash command: ### _print() { echo -n 'printf %q '$@ } echo '- - Outputs of function _print - -' _print parm1 parm2 # An Output NOT A Command. echo $(_print parm1 parm2) # Executes: printf %q parm1 parm2 # See above IFS examples for the #+ various possibilities. echo $(_print $VarSomething) # The predictable result. echo # Function variables # ------------------ echo echo '- - Function variables - -' # A variable may represent a signed integer, a string or an array. # A string may be used like a function name with optional arguments. # set -vx # Enable if desired declare -f funcVar #+ in namespace of functions funcVar=_print # Contains name of function. $funcVar parm1 # Same as _print at this point. echo funcVar=$(_print ) # Contains result of function. $funcVar # No input, No output. $funcVar $VarSomething # The predictable result. echo funcVar=$(_print $VarSomething) # $VarSomething replaced HERE. $funcVar # The expansion is part of the echo #+ variable contents. funcVar="$(_print $VarSomething)" # $VarSomething replaced HERE. $funcVar # The expansion is part of the echo #+ variable contents. # The difference between the unquoted and the double-quoted versions #+ above can be seen in the "protect_literal.sh" example. # The first case above is processed as two, unquoted, Bash-Words. # The second case above is processed as one, quoted, Bash-Word. # Delayed replacement # ------------------- echo echo '- - Delayed replacement - -' funcVar="$(_print '$VarSomething')" # No replacement, single Bash-Word. eval $funcVar # $VarSomething replaced HERE. echo VarSomething='NewThing' eval $funcVar # $VarSomething replaced HERE. echo # Restore the original setting trashed above. VarSomething=Literal # There are a pair of functions demonstrated in the #+ "protect_literal.sh" and "unprotect_literal.sh" examples. # These are general purpose functions for delayed replacement literals #+ containing variables. # REVIEW: # ------ # A string can be considered a Classic-Array of elements (characters). # A string operation applies to all elements (characters) of the string #+ (in concept, anyway). ### # The notation: ${array_name[@]} represents all elements of the #+ Bash-Array: array_name. ### # The Extended-Syntax string operations can be applied to all #+ elements of an array. ### # This may be thought of as a For-Each operation on a vector of strings. ### # Parameters are similar to an array. # The initialization of a parameter array for a script #+ and a parameter array for a function only differ #+ in the initialization of ${0}, which never changes its setting. ### # Subscript zero of the script's parameter array contains #+ the name of the script. ### # Subscript zero of a function's parameter array DOES NOT contain #+ the name of the function. # The name of the current function is accessed by the $FUNCNAME variable. ### # A quick, review list follows (quick, not short). echo echo '- - Test (but not change) - -' echo '- null reference -' echo -n ${VarNull-'NotSet'}' ' # NotSet echo ${VarNull} # NewLine only echo -n ${VarNull:-'NotSet'}' ' # NotSet echo ${VarNull} # Newline only echo '- null contents -' echo -n ${VarEmpty-'Empty'}' ' # Only the space echo ${VarEmpty} # Newline only echo -n ${VarEmpty:-'Empty'}' ' # Empty echo ${VarEmpty} # Newline only echo '- contents -' echo ${VarSomething-'Content'} # Literal echo ${VarSomething:-'Content'} # Literal echo '- Sparse Array -' echo ${ArrayVar[@]-'not set'} # ASCII-Art time # State Y==yes, N==no # - :- # Unset Y Y ${# ... } == 0 # Empty N Y ${# ... } == 0 # Contents N N ${# ... } > 0 # Either the first and/or the second part of the tests #+ may be a command or a function invocation string. echo echo '- - Test 1 for undefined - -' declare -i t _decT() { t=$t-1 } # Null reference, set: t == -1 t=${#VarNull} # Results in zero. ${VarNull- _decT } # Function executes, t now -1. echo $t # Null contents, set: t == 0 t=${#VarEmpty} # Results in zero. ${VarEmpty- _decT } # _decT function NOT executed. echo $t # Contents, set: t == number of non-null characters VarSomething='_simple' # Set to valid function name. t=${#VarSomething} # non-zero length ${VarSomething- _decT } # Function _simple executed. echo $t # Note the Append-To action. # Exercise: clean up that example. unset t unset _decT VarSomething=Literal echo echo '- - Test and Change - -' echo '- Assignment if null reference -' echo -n ${VarNull='NotSet'}' ' # NotSet NotSet echo ${VarNull} unset VarNull echo '- Assignment if null reference -' echo -n ${VarNull:='NotSet'}' ' # NotSet NotSet echo ${VarNull} unset VarNull echo '- No assignment if null contents -' echo -n ${VarEmpty='Empty'}' ' # Space only echo ${VarEmpty} VarEmpty='' echo '- Assignment if null contents -' echo -n ${VarEmpty:='Empty'}' ' # Empty Empty echo ${VarEmpty} VarEmpty='' echo '- No change if already has contents -' echo ${VarSomething='Content'} # Literal echo ${VarSomething:='Content'} # Literal # "Subscript sparse" Bash-Arrays ### # Bash-Arrays are subscript packed, beginning with #+ subscript zero unless otherwise specified. ### # The initialization of ArrayVar was one way #+ to "otherwise specify". Here is the other way: ### echo declare -a ArraySparse ArraySparse=( [1]=one [2]='' [4]='four' ) # [0]=null reference, [2]=null content, [3]=null reference echo '- - Array-Sparse List - -' # Within double-quotes, default IFS, Glob-Pattern IFS=$'\x20'$'\x09'$'\x0A' printf %q "${ArraySparse[*]}" echo # Note that the output does not distinguish between "null content" #+ and "null reference". # Both print as escaped whitespace. ### # Note also that the output does NOT contain escaped whitespace #+ for the "null reference(s)" prior to the first defined element. ### # This behavior of 2.04, 2.05a and 2.05b has been reported #+ and may change in a future version of Bash. # To output a sparse array and maintain the [subscript]=value #+ relationship without change requires a bit of programming. # One possible code fragment: ### # local l=${#ArraySparse[@]} # Count of defined elements # local f=0 # Count of found subscripts # local i=0 # Subscript to test ( # Anonymous in-line function for (( l=${#ArraySparse[@]}, f = 0, i = 0 ; f < l ; i++ )) do # 'if defined then...' ${ArraySparse[$i]+ eval echo '\ ['$i']='${ArraySparse[$i]} ; (( f++ )) } done ) # The reader coming upon the above code fragment cold #+ might want to review "command lists" and "multiple commands on a line" #+ in the text of the foregoing "Advanced Bash Scripting Guide." ### # Note: # The "read -a array_name" version of the "read" command #+ begins filling array_name at subscript zero. # ArraySparse does not define a value at subscript zero. ### # The user needing to read/write a sparse array to either #+ external storage or a communications socket must invent #+ a read/write code pair suitable for their purpose. ### # Exercise: clean it up. unset ArraySparse echo echo '- - Conditional alternate (But not change)- -' echo '- No alternate if null reference -' echo -n ${VarNull+'NotSet'}' ' echo ${VarNull} unset VarNull echo '- No alternate if null reference -' echo -n ${VarNull:+'NotSet'}' ' echo ${VarNull} unset VarNull echo '- Alternate if null contents -' echo -n ${VarEmpty+'Empty'}' ' # Empty echo ${VarEmpty} VarEmpty='' echo '- No alternate if null contents -' echo -n ${VarEmpty:+'Empty'}' ' # Space only echo ${VarEmpty} VarEmpty='' echo '- Alternate if already has contents -' # Alternate literal echo -n ${VarSomething+'Content'}' ' # Content Literal echo ${VarSomething} # Invoke function echo -n ${VarSomething:+ $(_simple) }' ' # SimpleFunc Literal echo ${VarSomething} echo echo '- - Sparse Array - -' echo ${ArrayVar[@]+'Empty'} # An array of 'Empty'(ies) echo echo '- - Test 2 for undefined - -' declare -i t _incT() { t=$t+1 } # Note: # This is the same test used in the sparse array #+ listing code fragment. # Null reference, set: t == -1 t=${#VarNull}-1 # Results in minus-one. ${VarNull+ _incT } # Does not execute. echo $t' Null reference' # Null contents, set: t == 0 t=${#VarEmpty}-1 # Results in minus-one. ${VarEmpty+ _incT } # Executes. echo $t' Null content' # Contents, set: t == (number of non-null characters) t=${#VarSomething}-1 # non-null length minus-one ${VarSomething+ _incT } # Executes. echo $t' Contents' # Exercise: clean up that example. unset t unset _incT # ${name?err_msg} ${name:?err_msg} # These follow the same rules but always exit afterwards #+ if an action is specified following the question mark. # The action following the question mark may be a literal #+ or a function result. ### # ${name?} ${name:?} are test-only, the return can be tested. # Element operations # ------------------ echo echo '- - Trailing sub-element selection - -' # Strings, Arrays and Positional parameters # Call this script with multiple arguments #+ to see the parameter selections. echo '- All -' echo ${VarSomething:0} # all non-null characters echo ${ArrayVar[@]:0} # all elements with content echo ${@:0} # all parameters with content; # ignoring parameter[0] echo echo '- All after -' echo ${VarSomething:1} # all non-null after character[0] echo ${ArrayVar[@]:1} # all after element[0] with content echo ${@:2} # all after param[1] with content echo echo '- Range after -' echo ${VarSomething:4:3} # ral # Three characters after # character[3] echo '- Sparse array gotch -' echo ${ArrayVar[@]:1:2} # four - The only element with content. # Two elements after (if that many exist). # the FIRST WITH CONTENTS #+ (the FIRST WITH CONTENTS is being #+ considered as if it #+ were subscript zero). # Executed as if Bash considers ONLY array elements with CONTENT # printf %q "${ArrayVar[@]:0:3}" # Try this one # In versions 2.04, 2.05a and 2.05b, #+ Bash does not handle sparse arrays as expected using this notation. # # The current Bash maintainer, Chet Ramey, has corrected this #+ for an upcoming version of Bash. echo '- Non-sparse array -' echo ${@:2:2} # Two parameters following parameter[1] # New victims for string vector examples: stringZ=abcABC123ABCabc arrayZ=( abcabc ABCABC 123123 ABCABC abcabc ) sparseZ=( [1]='abcabc' [3]='ABCABC' [4]='' [5]='123123' ) echo echo ' - - Victim string - -'$stringZ'- - ' echo ' - - Victim array - -'${arrayZ[@]}'- - ' echo ' - - Sparse array - -'${sparseZ[@]}'- - ' echo ' - [0]==null ref, [2]==null ref, [4]==null content - ' echo ' - [1]=abcabc [3]=ABCABC [5]=123123 - ' echo ' - non-null-reference count: '${#sparseZ[@]}' elements' echo echo '- - Prefix sub-element removal - -' echo '- - Glob-Pattern match must include the first character. - -' echo '- - Glob-Pattern may be a literal or a function result. - -' echo # Function returning a simple, Literal, Glob-Pattern _abc() { echo -n 'abc' } echo '- Shortest prefix -' echo ${stringZ#123} # Unchanged (not a prefix). echo ${stringZ#$(_abc)} # ABC123ABCabc echo ${arrayZ[@]#abc} # Applied to each element. # Fixed by Chet Ramey for an upcoming version of Bash. # echo ${sparseZ[@]#abc} # Version-2.05b core dumps. # The -it would be nice- First-Subscript-Of # echo ${#sparseZ[@]#*} # This is NOT valid Bash. echo echo '- Longest prefix -' echo ${stringZ##1*3} # Unchanged (not a prefix) echo ${stringZ##a*C} # abc echo ${arrayZ[@]##a*c} # ABCABC 123123 ABCABC # Fixed by Chet Ramey for an upcoming version of Bash # echo ${sparseZ[@]##a*c} # Version-2.05b core dumps. echo echo '- - Suffix sub-element removal - -' echo '- - Glob-Pattern match must include the last character. - -' echo '- - Glob-Pattern may be a literal or a function result. - -' echo echo '- Shortest suffix -' echo ${stringZ%1*3} # Unchanged (not a suffix). echo ${stringZ%$(_abc)} # abcABC123ABC echo ${arrayZ[@]%abc} # Applied to each element. # Fixed by Chet Ramey for an upcoming version of Bash. # echo ${sparseZ[@]%abc} # Version-2.05b core dumps. # The -it would be nice- Last-Subscript-Of # echo ${#sparseZ[@]%*} # This is NOT valid Bash. echo echo '- Longest suffix -' echo ${stringZ%%1*3} # Unchanged (not a suffix) echo ${stringZ%%b*c} # a echo ${arrayZ[@]%%b*c} # a ABCABC 123123 ABCABC a # Fixed by Chet Ramey for an upcoming version of Bash. # echo ${sparseZ[@]%%b*c} # Version-2.05b core dumps. echo echo '- - Sub-element replacement - -' echo '- - Sub-element at any location in string. - -' echo '- - First specification is a Glob-Pattern - -' echo '- - Glob-Pattern may be a literal or Glob-Pattern function result. - -' echo '- - Second specification may be a literal or function result. - -' echo '- - Second specification may be unspecified. Pronounce that' echo ' as: Replace-With-Nothing (Delete) - -' echo # Function returning a simple, Literal, Glob-Pattern _123() { echo -n '123' } echo '- Replace first occurrence -' echo ${stringZ/$(_123)/999} # Changed (123 is a component). echo ${stringZ/ABC/xyz} # xyzABC123ABCabc echo ${arrayZ[@]/ABC/xyz} # Applied to each element. echo ${sparseZ[@]/ABC/xyz} # Works as expected. echo echo '- Delete first occurrence -' echo ${stringZ/$(_123)/} echo ${stringZ/ABC/} echo ${arrayZ[@]/ABC/} echo ${sparseZ[@]/ABC/} # The replacement need not be a literal, #+ since the result of a function invocation is allowed. # This is general to all forms of replacement. echo echo '- Replace first occurrence with Result-Of -' echo ${stringZ/$(_123)/$(_simple)} # Works as expected. echo ${arrayZ[@]/ca/$(_simple)} # Applied to each element. echo ${sparseZ[@]/ca/$(_simple)} # Works as expected. echo echo '- Replace all occurrences -' echo ${stringZ//[b2]/X} # X-out b's and 2's echo ${stringZ//abc/xyz} # xyzABC123ABCxyz echo ${arrayZ[@]//abc/xyz} # Applied to each element. echo ${sparseZ[@]//abc/xyz} # Works as expected. echo echo '- Delete all occurrences -' echo ${stringZ//[b2]/} echo ${stringZ//abc/} echo ${arrayZ[@]//abc/} echo ${sparseZ[@]//abc/} echo echo '- - Prefix sub-element replacement - -' echo '- - Match must include the first character. - -' echo echo '- Replace prefix occurrences -' echo ${stringZ/#[b2]/X} # Unchanged (neither is a prefix). echo ${stringZ/#$(_abc)/XYZ} # XYZABC123ABCabc echo ${arrayZ[@]/#abc/XYZ} # Applied to each element. echo ${sparseZ[@]/#abc/XYZ} # Works as expected. echo echo '- Delete prefix occurrences -' echo ${stringZ/#[b2]/} echo ${stringZ/#$(_abc)/} echo ${arrayZ[@]/#abc/} echo ${sparseZ[@]/#abc/} echo echo '- - Suffix sub-element replacement - -' echo '- - Match must include the last character. - -' echo echo '- Replace suffix occurrences -' echo ${stringZ/%[b2]/X} # Unchanged (neither is a suffix). echo ${stringZ/%$(_abc)/XYZ} # abcABC123ABCXYZ echo ${arrayZ[@]/%abc/XYZ} # Applied to each element. echo ${sparseZ[@]/%abc/XYZ} # Works as expected. echo echo '- Delete suffix occurrences -' echo ${stringZ/%[b2]/} echo ${stringZ/%$(_abc)/} echo ${arrayZ[@]/%abc/} echo ${sparseZ[@]/%abc/} echo echo '- - Special cases of null Glob-Pattern - -' echo echo '- Prefix all -' # null substring pattern means 'prefix' echo ${stringZ/#/NEW} # NEWabcABC123ABCabc echo ${arrayZ[@]/#/NEW} # Applied to each element. echo ${sparseZ[@]/#/NEW} # Applied to null-content also. # That seems reasonable. echo echo '- Suffix all -' # null substring pattern means 'suffix' echo ${stringZ/%/NEW} # abcABC123ABCabcNEW echo ${arrayZ[@]/%/NEW} # Applied to each element. echo ${sparseZ[@]/%/NEW} # Applied to null-content also. # That seems reasonable. echo echo '- - Special case For-Each Glob-Pattern - -' echo '- - - - This is a nice-to-have dream - - - -' echo _GenFunc() { echo -n ${0} # Illustration only. # Actually, that would be an arbitrary computation. } # All occurrences, matching the AnyThing pattern. # Currently //*/ does not match null-content nor null-reference. # /#/ and /%/ does match null-content but not null-reference. echo ${sparseZ[@]//*/$(_GenFunc)} # A possible syntax would be to make #+ the parameter notation used within this construct mean: # ${1} - The full element # ${2} - The prefix, if any, to the matched sub-element # ${3} - The matched sub-element # ${4} - The suffix, if any, to the matched sub-element # # echo ${sparseZ[@]//*/$(_GenFunc ${3})} # Same as ${1} here. # Perhaps it will be implemented in a future version of Bash. exit 0