In string:
rtsp://user:pass@my.webserver.org:5554/my-media/media.amp?videocodec=h264
you have ?
in that string, so the shell will perform pathname expansion on that string, using pattern matching rules.
In bash
, if failglob
options was not set, which is default, then failed pattern will be left as-is:
$ echo does-not-exist?
does-not-exist?
While zsh
will report no pattern match error with nomatch
option set, which is default:
$ echo does-not-exist?
zsh: no matches found: does-not-exist?
You can make zsh
suppress the error and print the pattern:
$ setopt nonomatch
$ echo does-not-exist?
does-not-exist?
You can make bash
behave like zsh
with nomatch
option set, by turning on failglob
:
$ shopt -s failglob
$ echo does-not-exist?
bash: no match: does-not-exist?
More general, you can disable shell filename generation:
$ set -f
$ : "The command"
$ set +f
(or set -o noglob
, set +o noglob
)
or using one of shell quoting methods to make the shell treats ?
and other pattern matching special characters literally.
zsh
also provide the noglob
builtin, which disable filename generation in any words for the following simple command:
$ noglob echo *
*
Playing with spaces, tabs or newlines is always close to failure.
The core problem occurs here:
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 )
IFS=$'\n'
printf '<0 %s>\n' "${encl0[@]#0,}"
If executed on bash 3.2:
$ b32sh ./script
<0 0 1 2 3 4 5 7 8 9>
The expansion of "${encl0[@]#0,}"
is processed as one string, not a list of values.
The problem doesn't manifest if either the IFS has an space or if the expansion does not edit each value of the array:
#!/bin/bash
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 )
IFS=$' \n'
printf '<0 %s>\n' "${encl0[@]#0,}"
Executed:
$ b32sh ./script
<0 0>
<0 1>
<0 2>
<0 3>
<0 4>
<0 5>
<0 7>
<0 8>
<0 9>
Or:
#!/bin/bash
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 )
IFS=$'\n'
printf '<0 %s>\n' "${encl0[@]}"
Executed:
b32sh ./so
<0 0,0>
<0 0,1>
<0 0,2>
<0 0,3>
<0 0,4>
<0 0,5>
<0 0,7>
<0 0,8>
<0 0,9>
The problem is hidden in your script because you restore IFS IFS=$OLDIFS
before the testing echo line.
One way to avoid the issue is to not use an space in the printf:
#!/bin/bash
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 0,10 0,11 0,12 0,13 0,14 0,15 )
MISSING_DISKS=()
OLDIFS=$IFS
IFS=$' \n'
MISSING_DISKS+=($({ printf '0x%s\n' {0..15}; printf '0x%s\n' "${encl0[@]#0,}"; } | sort | uniq -u))
echo "test $({ printf '0x%s\n' {0..15}; printf '0x%s\n' "${encl0[@]#0,}"; } | sort | uniq -u)"
echo "var ${MISSING_DISKS[@]}"
if ((${#MISSING_DISKS[@]}>1)); then
echo "Greater than 1"
else
echo "Success"
fi
IFS=$OLDIFS
The other alternative is to avoid the expansion with substitution after changing the IFS to a newline by using an alternate array:
#!/bin/bash
OLDIFS=$IFS
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 0,10 0,11 0,12 0,13 0,14 0,15 )
IFS=$' \n'; arr=("${encl0[@]#0,}")
MISSING_DISKS=()
IFS=$'\n'
MISSING_DISKS+=($({ printf '0 %s\n' {0..15}; printf '0 %s\n' "${arr[@]}"; } | sort | uniq -u))
echo "test $({ printf '0 %s\n' {0..15}; printf '0 %s\n' "${arr[@]}"; } | sort | uniq -u)"
echo "var ${MISSING_DISKS[@]}"
if ((${#MISSING_DISKS[@]}>1)); then
echo "Greater than 1"
else
echo "Success"
fi
IFS=$OLDIFS
I recommend you that:
- Follow the rule that variables in CAPS are environment variables.
- There is no need to augment
+=
an array that is empty at that point in the code MISSING_DISKS+=
.
- Use a non-space character in the printf to avoid the need to remove the space from IFS latter. That makes the script more robust.
If those changes are done, the script becomes:
#!/bin/bash
oldIFS=$IFS
encl0=( 0,0 0,1 0,2 0,3 0,4 0,5 0,7 0,8 0,9 0,10 0,11 0,12 0,13 0,14 0,15 )
unset missing_disks
IFS=' '
arr=($(printf '0-%s\n' "${encl0[@]#0,}"))
arr+=($(printf '0-%s\n' {0..15}))
missing_disks=($(printf '%s\n' "${arr[@]}" | sort | uniq -u))
echo "test $(printf '0-%s\n' "${arr[@]}" | sort | uniq -u)"
echo "var ${missing_disks[@]}"
((${#missing_disks[@]}>1)) && echo "Greater than 1" || echo "Success"
IFS=$oldIFS
Best Answer
This is happening because
cut
is outputting NULL characters in the output. You can't pass a program arguments which contain a null character (see this).In bash this works because bash can't handle NULL characters in strings, and it strips them out. Zsh is a bit more powerful, and it can handle NULL characters. However when it comes time to pass the string to the program, it still contains the null, which signals the end of the argument.
Let's look at this in detail.
Here we simulated one of your files, passing the path through
cut
. Notice thexxd
output which has a NULL character betweenEnglish
andAU
.Now lets run through and simulate the rest of the script.
Notice the NULL after the
English
. Theecho
displays it properly becauseecho
is a shell built-in. If we use an externalecho
, it also exhibits the issue.P.S. You really should be quoting too :-)
The solution is to not use
cut
, useawk
instead.