Skip to content
Advertisement

Why does posh fail to perform pathname expansion when a part of the path is specified within double-quotes?

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”:

Both of them mention that pathname expansion occurs before quote removal.

  1. Pathname expansion (see Pathname Expansion) shall be performed, unless set -f is in effect.
  2. 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

  1. 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.

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement