开发者

Why using dirname in find command gives dots for each match?

I'm using find for a task and I noticed that when I do something like this:

find `pwd` -name "file.ext" -exec echo $(dirname {}) \;

it will give you dots only for each match. When you substitute dirname with basename in that command you get开发者_运维百科 the full pathnames. Am I screwing something up here or is this expected behavior? I'm used to basename giving you the name of the file (in this case file.ext) and dirname giving you the rest of the path.


Consider the following script:

#!/bin/sh
set -x
find `pwd` -name "file.ext" -exec echo $(dirname {}) \;

set -x shows how the expansion works and what the final command is. When run, it gives the following output:

++ pwd
++ dirname '{}'
+ find /home/kibab -name file.ext -exec echo . ';'

So, the first thing that is expanded is the pwd. Second is $(dirname {}). The result of those two commands is then dropped into the find command. Thus, you're telling find to -exec echo ., so you're seeing the expected output.

When you substitute basename for dirname, the expansion still takes places, but the results of the expansion are different:

  1. pwd is expanded to the current path. In my example above, the result is /home/kibab

  2. basename {} is executed. The result of this command is {}.

  3. The find command is executed with the above substitutions in place. The final command executed looks like this:

    find /home/kibab -name '*.png' -exec echo '{}' ';'

Upon inspecting the above command, you'll notice that the command now simply echo's whatever file was found.

Perhaps you want something like this?

find `pwd` -name "file.ext" -printf "%f\n"


So the problem is that $(...) or `...` starts a new shell before make the replacement.

Consider using bash -c:

$ find . -name '*.PNG' -exec bash -c 'git mv {} $(dirname {})/$(basename {} .PNG)48.png' \;

That renames any icon on a git repo to a more standard form.

Here {} is replaced before executing anything, so the problem is gone.

For that example, TMTOWTDI, but I try to keep it simple so you can start whatever you really need to do.


you do not have to call dirname() for each file found. with GNU find, you can use -printf and its faster this way

find /path -type f -iname "*.ext" -printf "%h\n"


$(dirname {}) is evaluated by the shell before being passed to find. The result of this evaluation is ., so you're just telling find to execute echo . for each file it finds.

basename {} evaluates to {}, so with $(basename {}) substituted for $(dirname {}), find will execute echo {} for each file. This results in the full name of each file being echoed.

If you want to output the result of dirname for each file found, you can omit the echo:

find `pwd` -name "file.ext" -exec dirname {} \;


It showing you the dots, because the process substitution is evaluated before the command is actually executed. So you've to pass your command into separate shell instance.

As for workaround, use the following syntax:

find $PWD -name "file.ext" -exec sh -c 'echo $(dirname {})' ';'

However the easiest way to print or execute something in each directory is by -execdir, e.g.:

find . -name "file.ext" -execdir pwd ';'


I don't know why you're getting that, but try this:

find `pwd` -name file.ext |xargs -l1 dirname


This is because find prints paths relative to the path it searches from. If you tried this search from / you would get ``pwd\ for each path.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜