foo=$*: ^A and DEL are prefixed or removed

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

foo=$*: ^A and DEL are prefixed or removed

Martijn Dekker
Here's another corner-case bug with assigning $* to a variable (i.e.:
foo=$*). If IFS is empty, the $* expansion removes any $'\001' (^A) and
$'\177' (DEL) characters. If IFS contains a value, each ^A and DEL
character is prefixed by another $'\001'. If IFS is unset, the bug does
not show up at all.

This is another case where quoting the $* (i.e.: foo="$*") works around
the bug, yet it's still a bug.

Test script:

fn() {
    foo=$*
    printf '%s' "$foo" | od -c | awk 'NR==1 { $1=""; print; }'
}
teststring=$(printf '\001\002\003\177')
for IFS in '' ' ' 'X' ' X'; do
    fn "$teststring"
done
unset -v IFS
fn "$teststring"

Expected output (and actual output from every non-bash shell):

 001 002 003 177
 001 002 003 177
 001 002 003 177
 001 002 003 177
 001 002 003 177

Actual output (bash 4.4.12, bash-20171110 snapshot):

 002 003
 001 001 002 003 001 177
 001 001 002 003 001 177
 001 001 002 003 001 177
 001 002 003 177

Actual output (bash 4.3.39, 4.2.53, 4.1.17, 3.2.57):

 001 002 003 177
 002 003
 001 002 003 177
 001 002 003 177
 001 002 003 177

Actual output (bash 2.05b):

 001 002 003
 001 002 003
 001 002 003
 001 002 003
 001 002 003

- Martijn

Reply | Threaded
Open this post in threaded view
|

Re: foo=$*: ^A and DEL are prefixed or removed

Chet Ramey
On 11/24/17 3:17 AM, Martijn Dekker wrote:
> Here's another corner-case bug with assigning $* to a variable (i.e.:
> foo=$*). If IFS is empty, the $* expansion removes any $'\001' (^A) and
> $'\177' (DEL) characters. If IFS contains a value, each ^A and DEL
> character is prefixed by another $'\001'. If IFS is unset, the bug does
> not show up at all.

Thanks for the report. I'll fix this for the next version.

--
``The lyf so short, the craft so long to lerne.'' - Chaucer
                 ``Ars longa, vita brevis'' - Hippocrates
Chet Ramey, UTech, CWRU    [hidden email]    http://tiswww.cwru.edu/~chet/

Reply | Threaded
Open this post in threaded view
|

Re: foo=$*: ^A and DEL are prefixed or removed

Martijn Dekker
Op 16-12-17 om 17:06 schreef Chet Ramey:
> On 11/24/17 3:17 AM, Martijn Dekker wrote:
>> Here's another corner-case bug with assigning $* to a variable (i.e.:
>> foo=$*). If IFS is empty, the $* expansion removes any $'\001' (^A) and
>> $'\177' (DEL) characters. If IFS contains a value, each ^A and DEL
>> character is prefixed by another $'\001'. If IFS is unset, the bug does
>> not show up at all.
>
> Thanks for the report. I'll fix this for the next version.

FWIW, here's a test script. It's POSIX compatible so you can compare
with other shells. Correct output is
        soh stx etx del   / soh stx etx del
for all cases.

- M.

defaultIFS=$IFS
set -o errexit -o noglob
(set -o pipefail) 2>/dev/null && set -o pipefail
teststring=$(printf '\1\2\3\177')
n=0

trim_od() {
        od -a | sed -n '1 { s/^0*[[:blank:]]*//; s/[[:blank:]]*$//; p; }'
}

doTest() {
        set -- "$teststring"
        eval "$testcmd"
        case ${IFS+s}${IFS:+n} in
        ( sn ) i=$(printf %s "$IFS" | trim_od) ;;
        ( s ) i='(null)' ;;
        ( '' ) i='(unset)' ;;
        ( * ) echo 'internal error!' >&2; exit 125 ;;
        esac
        printf '\n%03d: IFS = %s: %s\n' "$((n+=1))" "$i" "$testcmd"
        printf %s "$*${foo+/}${foo-}" | trim_od
}

doAllTests() {
        for testcmd in \
                'unset -v foo; set -- ${foo=$*}' \
                'unset -v foo; set -- ${foo="$*"}' \
                'unset -v foo; set -- "${foo=$*}"' \
                \
                'foo=; set -- ${foo:=$*}' \
                'foo=; set -- ${foo:="$*"}' \
                'foo=; set -- "${foo:=$*}"' \
                \
                'unset -v foo; set -- ${foo=$@}' \
                'unset -v foo; set -- ${foo="$@"}' \
                'unset -v foo; set -- "${foo=$@}"' \
                \
                'foo=; set -- ${foo:=$@}' \
                'foo=; set -- ${foo:="$@"}' \
                'foo=; set -- "${foo:=$@}"'
        do
                doTest "$testcmd"
        done
}

unset -v IFS; doAllTests
IFS=''; doAllTests
IFS='x'; doAllTests
IFS=$defaultIFS; doAllTests

Reply | Threaded
Open this post in threaded view
|

Re: foo=$*: ^A and DEL are prefixed or removed

Martijn Dekker
The latest development snapshot appears to have fixed this bug.

However, it introduced a new one: if the positional parameters are
empty, then

    foo=$*

produces a segfault.

Thanks,

- Martijn

Reply | Threaded
Open this post in threaded view
|

Re: foo=$*: ^A and DEL are prefixed or removed

Chet Ramey
On 1/4/18 8:56 AM, Martijn Dekker wrote:
> The latest development snapshot appears to have fixed this bug.
>
> However, it introduced a new one: if the positional parameters are
> empty, then
>
>     foo=$*
>
> produces a segfault.

Hmmm...I thought that was part of the test suite. I'll take a look.

Chet


--
``The lyf so short, the craft so long to lerne.'' - Chaucer
                 ``Ars longa, vita brevis'' - Hippocrates
Chet Ramey, UTech, CWRU    [hidden email]    http://tiswww.cwru.edu/~chet/