![使用 mv / rename 在 bash shell 脚本中使用多个通配符扩展变量](https://linux22.com/image/230515/%E4%BD%BF%E7%94%A8%20mv%20%2F%20rename%20%E5%9C%A8%20bash%20shell%20%E8%84%9A%E6%9C%AC%E4%B8%AD%E4%BD%BF%E7%94%A8%E5%A4%9A%E4%B8%AA%E9%80%9A%E9%85%8D%E7%AC%A6%E6%89%A9%E5%B1%95%E5%8F%98%E9%87%8F.png)
我为 Linux 编写了一个 bash shell 脚本,用于根据数据文件中指定的参数移动静态文件夹中的文件。
我已将该脚本简化为最小的可重现示例,以演示我遇到的问题:
#!/bin/bash
while IFS=" " read -r fldr matchStr
do
perlExp=\'s/^/\\/home\\/test\\/alpha\\/"$fldr"\\//\'
fullmatchStr=testfile\ -\ "$matchStr"\ -\ *.txt
rename --verbose "$perlExp" ${fullmatchStr}
done < test.dat
(请注意,我最初开始使用mv
而不是rename
,并且我可以使用任一命令。)
该脚本读取test.dat
,它是语法如下的文本文件:
folderName matchStr
...
该文件的一个简单示例test.dat
:
test001 *.foo.bar
test002 *.foo.baz
test003 bar.*.baz
此数据文件中允许使用通配符并且脚本支持通配符,这一点至关重要。此外,还需要支持文件名中的空格。
要移动的文件的文件名示例:
testfile - linux.foo.bar - 10101010 10101010.txt
testfile - unix.foo.bar - 01010101 10101010.txt
testfile - ubuntu.foo.baz - 10101010 01010101.txt
testfile - debian.foo.baz - 10101010 00000000.txt
testfile - bar.linus.baz - 11111111 01010101.txt
鉴于上述test.dat
文件,这些文件需要分别移动到这些文件夹:
/home/test/alpha/test001
/home/test/alpha/test001
/home/test/alpha/test002
/home/test/alpha/test002
/home/test/alpha/test003
我阅读了几个相关的 QA,包括:
- mv: 无法统计 shell 脚本中没有这样的文件或目录
- https://stackoverflow.com/questions/8748831/when-do-we-need-curly-braces-around-shell-variables
- 为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?
但我仍然没有得到我想要的结果。没有出现错误,但没有移动任何文件。
如何改进这段代码以使其按预期工作?
答案1
如果您使用的是基于 Perl 的rename
(似乎就是这种情况),那么最好不要使用 shell 字符串构建 Perl 代码。只需直接使用 Perl 代码即可完成使用 shell 代码可以完成的工作。
在这种情况下,在循环中,我会做类似的事情:
IFS=
target="/home/test/alpha/$fldr/" rename --verbose 's/^/$ENV{target}/' "testfile - "$matchStr" - "*.txt
target
这里,替换的是我们为命令设置的环境变量的值rename
,允许它使用 Perl 代码轻松获取它。此外,将 IFS 设置为空字符串会禁用分词,因此我们可以安全地用于$matchStr
通配符。 (但是,这里可能不需要它,我们可以只使用不带引号的内容,$matchStr
而不必担心分词,因为它是 的结果IFS=" " read -r
,但在不太可能的情况下,它包含制表符......)
答案2
主要是为了关注为什么没有效果也没有错误,这:
perlExp=\'s/^/\\/home\\/test\\/alpha\\/"$fldr"\\//\'
rename --verbose "$perlExp" ${fullmatchStr}
分配perlExp
并传递给带有单引号的perl
字符串。's/^/\/home\/test\/alpha\/...\//'
这是一个有效的 Perl 表达式,包含单个字符串常量。它只是没有效果,所以rename
不做任何改变。
也就是说,嵌入到 shell 变量中的引号只是数据,shell 不会再次解析变量扩展的结果。因此,您不应在值中添加额外的引号。考虑如何与和 printsfoo="'foo bar'"; echo "$foo"
相同。echo "'foo bar'"
'foo bar'
作为另一个问题,fullmatchStr=testfile\ -\ "$matchStr"\ -\ *.txt
将变量设置为 string testfile - ... - *.txt
,并且其中同时包含空格和 glob 字符,不带引号的扩展将首先将其拆分为testfile
, -
, ...
, ,.
并且*.txt
只有这样 glob 才会扩展,匹配以 . 结尾的任何文件名.txt
。要解决此问题,您需要通过设置IFS
为空字符串来禁用分词。
使用 Perl s///
,您还可以使用不同的字符作为分隔符,这对于路径可能会更好,因为您可以避免倾斜牙签效应。
所以,这样的事情可能会起作用:
#!/bin/bash
# disable word splitting in the script due to the unquoted expansion
IFS=
# the IFS assignment below only applies to 'read'
while IFS=" " read -r fldr matchStr
do
perlExp='s#^#/tmp/test/alpha/'"$fldr"'/#'
fullmatchStr="testfile - $matchStr - *.txt"
rename --verbose "$perlExp" ${fullmatchStr}
done < test.dat
但老实说,穆鲁的解决方案通过环境传递字符串的方法更好,因为它避免了在代码中嵌入数据时所需的关心和两级引用,特别是您不需要关心从文件中获取的目录名称是否包含特殊字符到珀尔。