skip to Main Content

When expanding ${*:0:80} which should display the 1st chars of $1-$n, bash will prepend $0 and won’t honor the 80 size limit.

Even wired, if I do some shift, $0 is still displayed while, shifted element have disappeared.

le say we create the /tmp/test.sh (chmod +x /tmp/test.sh)

#!/bin/bash

A=$1
shift
B=$2
shift
C="$*"
echo "test1: ${*:0:80}"
echo "test2: ${C:0:80}"

When running it on CentOS-7.6.1810 bash (bash-4.2.46-31.el7.x86_64), here is the strange output: (test1 wrong ($0 printed and message not truncated to 80 chars), test2 ok)

/tmp/test.sh word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 word15 word16 word17 word18 word19 word20
test1: /tmp/test.sh word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 word15 word16 word17 word18 word19 word20
test2: word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 wor

When running it on MacOS Mojav 10.14.6 (version 3.2.57(1)-release (x86_64-apple-darwin18)),
here is another strange output: (test1 still wrong (not truncated), test2 ok)

/tmp/test.sh word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 word15 word16 word17 word18 word19 word20
test1: word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 word15 word16 word17 word18 word19 word20
test2: word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 wor

IMHO, Expected result should be:

  • $0 shouldn’t be outputted
  • output ($2-$n) should be truncated to 80 chars

Do I miss something or do I hit a bash bug or is this inconsistency across Oses expected?

2

Answers


  1. Substring expansion for * is not documented at all, though it is for @:

    If parameter is @, the result is length positional parameters beginning at offset. A negative
    offset is taken relative to one greater than the greatest positional parameter, so an offset
    of -1 evaluates to the last positional parameter. It is an expansion error if length evalu-
    ates to a number less than zero.

    That is, substring expansion selects a range of positional parameters (including, for some reason, the special parameter $0, probably to make indexing intuitive); it does not apply substring expansion to each parameter individually. The documentation has no mention of what happens if the parameter is * (though it explicitly defines the behavior of an array indexed by either @ or *).

    ${*:x:y} does appear to behave identically to ${@:x:y}, though it isn’t clear whether this is an artifact of the implementation that could change or if there is an oversight in the documentation.

    zsh does define ${*:0:80} as the first 80 characters of the single string resulting from the concatenation of the positional parameters, for what it’s worth.

    Login or Signup to reply.
  2. In a ${var:off:len} expansion form, if the var is * or @, the off and len will select a number of len positional parameters starting with index off instead of a substring of var:

    foo(){
        printf '{%s}n' "${*:2:2}"
        printf '[%s]n' "${@:2:2}"
    }
    IFS=, foo 1st 2nd 3rd 4th 5th
    
    {2nd,3rd}
    [2nd]
    [3rd]
    

    Notice the difference between $* when $@ when quoted: the first will join the parameters into a single word with the first character of the IFS variable, the second will expand each parameter into a separate word.

    This is consistent with how arrays indexed by [*] or [@] work, and is the same as in ksh93 (where IIRC the ${var:off:len} construct first appeared).

    In the bash docs, this is not documented for $*, but only for $@ and for the arrays indexed by [*] or [@].

    But the source code follows the same paths and diverge at the same points for * or @: so while it is perfectly possible that they may change it one day by adding a special case (maybe just out of spite ;-)), I find it highly unlikely.

    For completion, this is a snippet from ksh93‘s manpage, which describes this behavior:

    ${parameter:offset:length}
    ${parameter:offset}

    If parameter is * or @, or is an array name indexed by * or @, then offset and length refer to the array index and number of elements respectively. A negative offset is taken relative to one greater than the highest subscript for indexed arrays. The order for associate arrays is unspecified.


    This is very different in zsh (but even there, it is not inconsistent between positional parameters and arrays): when either $*, an array subscripted by * or a simple array name is used, all the elements will be first joined into a string, then the ${var:off:len} will extract a substring from it:

    $ zsh -c 'IFS=-; printf "{%s}n" "${*:0:8}"' zsh-c foo bar baz
    {foo-bar-}
    $ zsh -c 'arr=(foo bar baz); IFS=-; printf "{%s}n" "${arr[*]:0:8}"'
    {foo-bar-}
    

    In bash, if you want to join the positional arguments into a string, and extract a substring of it, use an intermediate variable:

    foo(){ local IFS=' '; local s="$*"; printf '{%s}n' "${s:3:8}"; }
    foo 1st 2nd 3rd
    
    { 2nd 3rd}
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search