0

I ran into the following problem:

user@machine:/$ echo ./dir/fil | xargs -I {} bash -c "echo $(basename {})"
./dir/fil

Why is it not just printing fil?

So basename seems to get the expected parameter ./dir/fil but somehow assumes it is all the filename. Conversly when used with dirname it only prints out .. It feels as if the /s are somehow escaped.

Why do I care?

I actually need to run something like this:

find -name "*.foo" | xargs -I {} bash -c "cd $(dirname \"{}\"); thirdpartytool $(basename \"{}\") 2>&1 > /dev/null | sort"
  • I need a non-zero return code if any call of thirdpartytool returns one, so find ... -exec ... does not work for me.
  • I need output redirection (discard stdout and sort stderr) so I need to call another shell.
  • I need to cd because thirdpartytool must be called from the directory where the file is, so I need dirname and basename in a subshell.
SebDieBln
  • 103
  • 2

1 Answers1

1
echo ./dir/fil | xargs -I {} bash -c "echo $(basename {})"

Here $(basename {}) is double-quoted and therefore it gets expanded by the current shell before xargs even runs. The output from basename {} is {}, so the command becomes:

echo ./dir/fil | xargs -I {} bash -c "echo {}"

It would be different if you single-quoted the shell code. Still embedding {} in shell code is wrong. You should pass the result of expansion of {} as positional parameter(s).

Eventually you want to use find. The truly robust command will be in a form of find … -print0 | xargs -0 … (if supported) or find … -exec ….

If I understand your goal right, your command will be like:

find . -name "*.foo" -print0 \
| xargs -0 -L1 sh -c 'cd "$(dirname "$1")" && thirdpartytool "$(basename "$1")" 2>&1 >/dev/null | sort' sh

where

  • I fixed the quoting,
  • I made sure the thirdpartytool runs only if cd succeeds,
  • I used sh because there is nothing specific to Bash in the shell code.

Additional notes:

Kamil Maciorowski
  • 69,815
  • 22
  • 136
  • 202