Consider the following simple shell script:
rm -rf bar "bar" mkdir -p bar touch bar/baz echo "bar"/*
I get the expected output with bash, ksh, zsh and dash, but I don’t get it with posh:
susam@debian:~$ bash foo.sh bar/baz susam@debian:~$ ksh foo.sh bar/baz susam@debian:~$ zsh foo.sh bar/baz susam@debian:~$ dash foo.sh bar/baz susam@debian:~$ posh foo.sh bar/*
I am trying to understand if the behaviour of posh is correct as per the POSIX standard or if it is a bug.
The relevant section in the POSIX documents seem to be “2.6 Word Expansion”:
- POSIX.1-2004: 2.6 Word Expansions
- POSIX.1-2008: 2.6 Word Expansions
Both of them mention that pathname expansion occurs before quote removal.
- Pathname expansion (see Pathname Expansion) shall be performed, unless
set -f
is in effect.- Quote removal (see Quote Removal) shall always be performed last.
Considering this, the posh behaviour looks right because "bar"/*
does not literally match any path above before the quote removal, so path expansion does not occur.
So this led me to suspect that if there were a directory literally named "bar"
, i.e. the quotes were part of the directory name, then posh would have matched it. But the following altered script shows that this is not true.
rm -rf bar "bar" mkdir -p "bar" touch "bar"/baz echo "bar"/*
Here is the output:
susam@debian1:~$ bash foo2.sh bar/* susam@debian1:~$ ksh foo2.sh bar/* susam@debian1:~$ zsh foo2.sh foo2.sh:3: no matches found: bar/* susam@debian1:~$ dash foo2.sh bar/* susam@debian1:~$ posh foo2.sh bar/*
So the pattern "bar"/*
in posh matches neither the path bar/baz
nor the path "bar"/baz
. What does it match then? Is the posh behaviour a bug or a feature?
Here are the version details in case it helps you to helps me:
susam@debian:~$ cat /etc/debian_version 8.3 susam@debian:~$ dpkg -l bash ksh zsh dash posh Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) ||/ Name Version Architecture Description +++-===========================-==================-==================-============================================================ ii bash 4.3-11+b1 amd64 GNU Bourne Again SHell ii dash 0.5.7-4+b1 amd64 POSIX-compliant shell ii ksh 93u+20120801-1 amd64 Real, AT&T version of the Korn shell ii posh 0.12.3 amd64 Policy-compliant Ordinary SHell ii zsh 5.0.7-5 amd64 shell with lots of features
Advertisement
Answer
This is a bug in posh
— see bug #636601. It is still open as of posh
version 0.12.6.
Attached to the discussion of that bug you’ll find a patch. When applying that, posh
behaves similar to bash
(so in your first example echo "bar"/*
gives bar/baz
).
Furthermore, that behaviour of bash
(and patched posh
) does conform to POSIX. The standard says
- Quote removal (see Quote Removal) shall always be performed last.
This is meant literally, as a purely syntactical action to remove protective quotes in the very last step. The semantical meaning of quotes will still apply in earlier steps, as pointed out by hek2mgl. (Otherwise, e.g. a quotation like "*"
would not have any effect at all.)
So on second thought, this conclusion is not correct:
Considering this, the posh behaviour looks right because “bar”/* does not literally match any path above before the quote removal, so path expansion does not occur.