我有一个有效的PHP命令:
php ~/.composer/vendor/bin/upgrade-code add-namespace "Vendor\Module\Finaldir" mymodule/finaldir --write -r -vvv" \;
我想将它包装成find -exec
调用(或类似),以便递归地为文件夹执行此操作。
find mymodule/src -mindepth 1 -maxdepth 2 -type d -exec sh -c "DIR=$(basename {}); php ~/.composer/vendor/bin/upgrade-code add-namespace "Vendor\\Module\\${DIR}" {} --write -r -vvv" \;
不幸的是,上面的代码不起作用(并且也不包括basename(DIR)变量的资本)。
实现这一目标的最佳方法是什么?
我根本找不到结婚,我很乐意使用其他任何东西,但希望我能把它保持在一条线上。
非常感谢你!
答案 0 :(得分:1)
这主要是一个shell引用问题,与find
无关。
在Unix shell(例如bash)中,变量扩展如${dir}
由shell执行,然后传递给命令行实用程序,除非它们包含在单引号中 。您可以使用echo
命令查看结果:
$ dir=/home/rici
$ echo $dir # Unquoted; the expansion is performed
/home/rici
$ echo "$dir" # Double-quoted; the expansion is performed
/home/rici
$ echo '$dir' # Single-quoted; the string is passed unmodified
$dir
(注意:你不应该使用all-caps shell变量名。全部大写的变量名,如$PATH
和$USER
,保留供系统使用。你自己的变量名称应为小写。)
普通版本和双引号版本之间的区别在于双引号版本保留了空白和文件模式字符,而未引用版本有效地将扩展结果拆分为单独的单词然后尝试扩展文件结果中的模式:
$ ls # Files in this directory
a b
$ dir='go *' # Note extra spaces
$ echo $dir # The variable's value is re-split and expanded
go a b
$ echo "$dir" # Just the value of the variable
go *
$ echo '$dir' # Just the characters of the argument
$dir
请注意,在未加引号的版本中,go
后面的三个空格已消失。 $dir
的值已分为两个单词,然后由于第二个单词是文件模式,它将扩展为两个文件名。所以echo
会被调用三个参数 - go
,a
和b
- 它会用一个空格分隔它们。
当命令行实用程序是shell解释器时,这会影响所使用的变量是否属于外壳(并且在命令行传递到内壳之前展开),或者作为文本字符串传递给内部shell shell,然后在执行时扩展它:
$ dir=outer
$ bash -c "dir=inner; echo $dir"
outer
$ bash -c 'dir=inner; echo $dir'
inner
这些公式都没有真正正确,因为内壳看到了不带引号的参数扩展,这可能导致不必要的单词拆分和文件名扩展。通常,我们使用的版本是:
bash -c 'dir=inner; echo "$dir"'
(在这种情况下,由于双引号在单引号内,它们也被外壳视为普通字符,因此它们被传递到内壳。)
find -exec bash -c
的一个问题是find -exec
插入命令行的文件名按原样插入。这样可以将精心设计的文件名用作injection attack。
为了避免这种情况,我们通常利用bash -c
也允许我们将位置参数传递给shell的事实。在要解释的命令之后bash -c
的参数被分配给$0
,$1
,依此类推。由于$0
不是真正的位置参数 - 它应该是脚本的名称 - 我们通常在命令参数之后和真实之前放置一个伪参数(_
在下面的例子中)位置论证。所以我们最终会得到这样的结果:
find ... -exec bash -c 'echo "$1"' _ {} \;
在这里,我仔细单引命令行参数,以便按原样传递;在命令行参数内部,我引用参数扩展,以便空格和星号不会引起任何问题,然后我得find -exec
将文件名作为第三个参数传递给bash -c
,其中它将成为$1
。我希望这一切都清楚。
因此,回到原来的问题,我们可以应用上述模式。但是我要做一点改变;而不是使用basename
来查找文件名的最后一个组件,我将使用稍微神秘的shell语法来删除变量的前缀。 ${var##*/}
取$var
的值,然后删除与模式*/
匹配的最长前缀。 (单个#
将是最短的匹配。)匹配*/
的最长前缀是整个目录路径,因为它延伸到最后/
;删除之后剩下的就是基本名称。
find mymodule/src -mindepth 1 -maxdepth 2 -type d \
-exec sh -c 'dir=${1##*/}; php ~/.composer/vendor/bin/upgrade-code add-namespace "Vendor\\Module\\$dir" "$dir" --write -r -vvv' _ {} \;