Subject: Re: bug#14023: dirname/basename unexpected results when run by xargs -I

From: Juho-Pekka Kuitunen <[email-masked]>
Date: Sat, 23 Mar 2013 13:03:11 +0200

On Fri, Mar 22, 2013 at 5:11 AM, Eric Blake <[email-masked]> wrote:
> On 03/21/2013 08:52 PM, Juho-Pekka Kuitunen wrote:
>> On Fri, Mar 22, 2013 at 3:05 AM, Bob Proulx <[email-masked]> wrote:
>>> Juho-Pekka Kuitunen wrote:
>>>> Reproduce example;
>>>> $ echo "testdir/testfile" | xargs -I '{}' echo '{}', dir: $(echo dirname '{}') = $(dirname '{}')
>
>>> The problem is the $(...) which is running the dirname during the
>>> earlier shell command line parsing pass and passing the result off to
>>> the xargs command.
>>
>> So if I understand correctly, subshell replacements gets executed
>> before xargs fills in the replacements? I tried to rule out this
>> wonkyness with the "$(echo '{}')" bit and it seemed to work in the
>> correct order. The echo should produce an empty string if the subshell
>> was evaluated too soon?
>
> If you want to see when the shell is evaluating $(), use 'set -vx':
>
> $ set -vx
> $ echo testdir/testfile | xargs -I '{}' echo '{}', dir: $(echo dirname
> '{}') = $(dirname '{}')
> echo testdir/testfile | xargs -I '{}' echo '{}', dir: $(echo dirname
> '{}') = $(dirname '{}')
> + echo testdir/testfile
> echo dirname '{}')
> dirname '{}')
> echo dirname '{}')
> echo dirname '{}'
> ++ echo dirname '{}'
> dirname '{}')
> dirname '{}'
> ++ dirname '{}'
> + xargs -I '{}' echo '{},' dir: dirname '{}' = .
> testdir/testfile, dir: dirname testdir/testfile = .
> $ set -
> set -
> + set -
>
> What you seem to want to do is invoke a shell instance on each file
> name; that would be done as follows:
>
> $ echo testdir/testfile | xargs -I {} sh -c \
>     'echo "$1", dir: $(echo dirname "$1") = $(dirname "$1")' sh {}
> testdir/testfile, dir: dirname testdir/testfile = testdir
>
> Or again with shell tracing (note there are two levels of shells - the
> shell that spawns xargs, and the shell that xargs spawns, so I'm posting
> two different traces):
>
> $ set -vx
> $ echo testdir/testfile | xargs -I {} sh -c 'echo "$1", dir: $(echo
> dirname "$1") = $(dirname "$1")' sh {}
> echo testdir/testfile | xargs -I {} sh -c 'echo "$1", dir: $(echo
> dirname "$1") = $(dirname "$1")' sh {}
> + xargs -I '{}' sh -c 'echo "$1", dir: $(echo dirname "$1") = $(dirname
> "$1")' sh '{}'
> + echo testdir/testfile
> testdir/testfile, dir: dirname testdir/testfile = testdir
> $ set -
> set -
> + set -
> $ echo testdir/testfile | xargs -I {} sh -cvx 'echo "$1", dir: $(echo
> dirname "$1") = $(dirname "$1")' sh {}
> echo "$1", dir: $(echo dirname "$1") = $(dirname "$1")
> echo dirname "$1")
> echo dirname "$1"
> ++ echo dirname testdir/testfile
> dirname "$1")
> dirname "$1"
> ++ dirname testdir/testfile
> + echo testdir/testfile, dir: dirname testdir/testfile = testdir
> testdir/testfile, dir: dirname testdir/testfile = testdir
>
> By the way, your question is mostly related to shell, and a bit with
> xargs, and practically nothing to do with dirname.  Your confusion on
> WHEN $() is expanded would apply no matter what executable you plug in
> instead of dirname.  But since neither sh nor xargs belongs to
> coreutils, you might get better answers by asking on a general forum on
> shell programming subtleties.
>
>>
>> Very much possibly not a bug but I'm not 100% convinced yet. I've been
>> chuckling at this all evening, love a good brain teaser even if it
>> turns out to be something I overlooked instead of a bug. :-)
>
> Hopefully shell tracing has managed to convince you.
>

Yup, that did the trick. Looks like I didn't understand the way xargs
works nearly well enough. Thanks for the lesson, my bad!

> --
> Eric Blake   eblake redhat com    +1-919-301-3266
> Libvirt virtualization library http://libvirt.org
>

--
Juho-Pekka Kuitunen