如何使用bash中的`${i%.mp3}`语法与xargs命令?

3

使用示例(虚构):将.mp3文件重命名为.txt文件。

我希望能够像这样做一些事情

find -name '*.mp3' -print0 | xargs -0 -I'file' mv file ${file%.mp3}.txt

这种方法不起作用,所以我用管道将find的输出通过sed -e 's/.mp3//g'进行处理,这样就可以解决问题。

但这似乎有点hackish(笨拙的);是否有一种使用通常的bash ${x%y}语法的方法?


你提出了一个没有纯粹答案的问题,或者至少是一个你不愿意接受的答案,因为你从未接受过。我的“hackish”方法是通过管道传输find ... | sed ... | xargs ...。这是你提到的替代方法。所以,明确地说明,xargs本身不支持Bash变量扩展。你能用很多不同的方法解决这个问题吗?正如你已经知道的那样,是的!你能在xargs中“纯粹地”完成这件事吗?不行! - J. M. Becker
5个回答

2
不,xargs -0 -I'file' mv file ${file%.mp3}.txt 不会生效,因为 file 变量将被 xargs 程序扩展而不是 shell。实际上,将其称为变量并不完全正确,在 xargs 的手册中称之为“replace-str”。
更新:如果只使用 xargs 和 bash(而没有 sed 或类似工具),当然可以让 xargs 启动一个 bash 进程,然后该进程可以执行任何您想要的替换操作。
find -name '*.mp3' -print0 | xargs -0 bash -c \
'while [ -n "$1" ]; do mv "$1" "${1%.mp3}.txt" ; shift; done;' "bash"

有没有办法让 shell 在 xargs 执行之前展开表达式? - user428502
“bash”的原因是它被解释为$0,所以$1变成了由xargs输入的内容。 - momeara

1

只是想再提供一个建议,即使它不使用 shell 的替换机制,而是使用 sed。

find -name '*.mp3' -print |
sed -e 's/\.mp3$//' -e "s/.*/mv '&\.mp3' '&\.txt'/" |
sh

换言之,为每个文件名创建一个mv命令行,并将结果传递给sh
如果任何一个文件的文件名中有撇号(单引号),这将失败。我主要发布这篇文章是为了对抗“如果你只有xargs,那么每个问题都会变成钉子”的综合症。
并非所有的替换表达式语法完全相同。特别地,我不确定在某些老旧的SunOS上是否可以有多个-e参数(请查找类似于/usr/ucb/blah/barf/vomit/xpg4/bin/sed的XPG兼容sed)。

1
对于当代的Bash,没问题。但不能在真正老旧的Bash版本中运行,而帖子中的原始问题可能就是这种情况,因此我尽量使其可移植性强。这应该可以在Heirloom sh和其他类似受限shell中运行。 - tripleee

1
如果您已经安装了GNU Parallel:
find -name '*.mp3' -print0 | parallel -0 mv {} {.}.txt

如果您的文件名不包含 \n(换行符),这个方法也可以使用:
find -name '*.mp3' | parallel mv {} {.}.txt

观看介绍视频以了解有关GNU Parallel的更多信息 http://www.youtube.com/watch?v=OpaiGYxkSuQ

0
find . -type f -name '*.mp3' | while read -r F
do
  mv "$F" "${F%.mp3}.txt"
done

Bash 4+

shopt -s globstar
shopt -s nullglob
for file in **/*.mp3
do
    mv "$file" "${file%.mp3}.txt"
done

1
我知道如何做到这一点;问题的目的是看是否可以使用xargs来实现,而不会立即扩展replace-str(我猜)。 - user428502

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接