修改 xargs 中的替换字符串

Posted

技术标签:

【中文标题】修改 xargs 中的替换字符串【英文标题】:Modifying replace string in xargs 【发布时间】:2012-06-03 22:06:57 【问题描述】:

当我使用xargs 时,有时我不需要显式使用替换字符串:

find . -name "*.txt" | xargs rm -rf

在其他情况下,我想指定替换字符串以执行以下操作:

find . -name "*.txt" | xargs -I '' mv '' /foo/''.bar

前面的命令会将当前目录下的所有文本文件移动到/foo,并将扩展名bar添加到所有文件中。

如果我不想在替换字符串中附加一些文本,而是想修改该字符串以便在文件的名称和扩展名之间插入一些文本,我该怎么做?例如,假设我想做与上一个示例相同的操作,但文件应该从<name>.txt 重命名/移动到/foo/<name>.bar.txt(而不是/foo/<name>.txt.bar)。

更新:我设法找到了解决方案:

find . -name "*.txt" | xargs -I \
    sh -c 'base=$(basename $1) ; name=$base%.* ; ext=$base##*. ; \
           mv "$1" "foo/$name.bar.$ext"' -- 

但我想知道是否有更短/更好的解决方案。

【问题讨论】:

不,除了我会使用更多的引用 mv "$1" "foo/$name.bar.$ext" 而你可以这样做 basename 像这样:base=$1##*/。您应该发布您的解决方案作为答案并接受它。 @DennisWilliamson 感谢您的评论!我再等一会儿,看看有没有人想出什么花哨的东西,否则我会自己回答问题。 我认为如果你的文件名是最后一行,你不需要 -I 或 在命令行中。 (请注意,xargs 的真正目的是进行分组,因此如果您不希望在 1 个 xargs 参数调用结束时需要多个东西,则需要“-l 1”(或 -L 1 用于某些版本的 xargs)。 -I 意味着 -l 1,这就是为什么它也适用于此。) sh -c 对我来说是关键!谢谢!否则我在echo |sed '...' 中的 cut/sed 命令未执行 【参考方案1】:

以下命令使用 xargs 构造移动命令,替换第二次出现的 '.'使用 '.bar.',然后使用 bash 执行命令,在 mac OSX 上工作。

ls *.txt | xargs -I  echo mv  foo/ | sed 's/\./.bar./2' | bash

【讨论】:

我会在这里使用echo 而不是ls【参考方案2】:

可以一次性完成(在 GNU 中测试)避免使用临时变量赋值

find . -name "*.txt" | xargs -I sh -c 'mv "$1" "foo/$(basename $1%.*).new.$1##*."' -- 

【讨论】:

我喜欢你使用替换字符串作为 shell 的位置参数的方式,这样你就可以执行替换和扩展。很酷。 很想知道你最后为什么使用 -- 而不是简单的 。我的意思是为什么不呢?找 。 -name ".txt" | xargs -I sh -c 'mv "$0" "foo/$(basename $0%.).new.$1##*."' @mac: (necroposting,抱歉!).. 因为在处理符合*.txt 模式的文件时,您永远无法提前知道会发生什么。您可能会获得以“-”开头的文件,并且您希望避免将它们与选项混淆。【参考方案3】:

在这种情况下,while 循环会更具可读性:

find . -name "*.txt" | while IFS= read -r pathname; do
    base=$(basename "$pathname"); name=$base%.*; ext=$base##*.
    mv "$pathname" "foo/$name.bar.$ext"
done

请注意,您可能会在不同的子目录中找到具有相同名称的文件。你可以接受mv 覆盖的重复项吗?

【讨论】:

是的,我知道它有那个“问题”。实际上,我发布的示例相当愚蠢。我只是为了在我的问题中展示一个简单的例子而编造它,所以没问题:) 要处理包含换行符的文件名,请使用:find . -name "*.txt" -print0 | while read -r -d '' pathname; do ... 请注意,如果您尝试使用 --max-procs 并行化 xargs,则此(串行)方法将不起作用。【参考方案4】:

如果您安装了 GNU Parallel http://www.gnu.org/software/parallel/,您可以这样做:

find . -name "*.txt" | parallel 'ext=/ ; mv --  foo//..bar."$ext##*."'

观看 GNU Parallel 的介绍视频以了解更多信息: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

【讨论】:

我还推荐 GNU Parallel 而不是 xargs,它更强大、更灵活。至于它的安装,我强烈建议使用你发行版的包管理器(例如sudo apt-get install parallel)或并行推荐的安装过程(根据git.savannah.gnu.org/cgit/parallel.git/tree/README):(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash @MestreLion 请不要推荐 curl-pipe-to-shell 反模式。 pi.dk 是什么?为什么我应该信任该域? gnu.org 至少更值得信赖。但两个 URL 都没有使用 TLS,更重要的是,没有一个 URL 验证下载内容的加密签名。唯一推荐的解决方案是apt-get 如您所见,pi.dk/3 会验证加密签名并在签名不匹配时停止。如果您不喜欢将其直接通过管道传输到 shell,您可以将其保存到文件中,检查代码,然后运行它。 @blujay:这不是 我的 建议,这是取自 官方文档:gnu.org/software/parallel/parallel_tutorial.html#Prerequisites。虽然我同意这不如使用发行版的包管理器好,但它比简单的wget ... && chmod +x好得多 @MestreLion 很遗憾它在 Parallel 站点上,您不应该进一步传播它。它并不比wget ... 好,因为它是dangerous for many reasons。这无异于告诉 Windows 用户从随机网站运行exe,而不对其进行病毒扫描。直接说不吧。【参考方案5】:

如果你被允许使用 bash/sh 以外的东西,而且这只是一个花哨的“mv”……你可以试试古老的“rename.pl”脚本。我一直在 Linux 上使用它,在 windows 上使用 cygwin。

http://people.sc.fsu.edu/~jburkardt/pl_src/rename/rename.html

rename.pl 's/^(.*?)\.(.*)$/\1-new_stuff_here.\2/' list_of_files_or_glob

您也可以使用“-p”参数来重命名.pl,让它告诉您它会做什么,而无需实际执行。

我刚刚在我的 c:/bin(cygwin/windows 环境)中尝试了以下操作。我使用了“-p”,所以它吐出了它会做什么。这个例子只是拆分了基础和扩展,并在它们之间添加了一个字符串。

perl c:/bin/rename.pl -p 's/^(.*?)\.(.*)$/\1-new_stuff_here.\2/' *.bat

rename "here.bat" => "here-new_stuff_here.bat"
rename "htmldecode.bat" => "htmldecode-new_stuff_here.bat"
rename "htmlencode.bat" => "htmlencode-new_stuff_here.bat"
rename "sdiff.bat" => "sdiff-new_stuff_here.bat"
rename "widvars.bat" => "widvars-new_stuff_here.bat"

【讨论】:

感谢您的回答! mv example 只是一个例子。我需要在很多其他情况下做类似的事情,所以我猜rename.pl 不适用于这些情况。 嗯,是的,也不是;您可以轻松地将 rename.pl 修改为 NOT 实际上重命名文件,但可以用它做其他事情。如您所见,使用“-p”参数,它根本不会重命名,而只是打印转换的结果。您可以使用该脚本作为基础来做许多其他事情,它采用的参数是任意 perl 代码,它非常强大。但是你当然是受欢迎的。【参考方案6】:

文件应该从<name>.txt重命名/移动到/foo/<name>.bar.txt

您可以使用rename 实用程序,例如:

rename s/\.txt$/\.txt\.bar/g *.txt

提示:替换语法类似于sedvim

然后使用mv将文件移动到某个目标目录:

mkdir /some/path
mv *.bar /some/path

要根据文件名的某些部分将文件重命名为子目录,请检查:

-p/--mkpath/--make-dirs在目标路径中创建任何不存在的目录。


测试:

$ touch 1..5.txt
$ rename --dry-run "s/.txt$/.txt.bar/g" *.txt
'1.txt' would be renamed to '1.txt.bar'
'2.txt' would be renamed to '2.txt.bar'
'3.txt' would be renamed to '3.txt.bar'
'4.txt' would be renamed to '4.txt.bar'
'5.txt' would be renamed to '5.txt.bar'

【讨论】:

【参考方案7】:

此外,wikipedia article 提供了令人惊讶的信息

for example:

Shell 技巧

实现类似效果的另一种方法是使用 shell 作为启动命令,并处理该 shell 中的复杂性,例如:

$ mkdir ~/backups
$ find /path -type f -name '*~' -print0 | xargs -0 bash -c 'for filename; do cp -a "$filename" ~/backups; done' bash

【讨论】:

【参考方案8】:

受上面@justaname 回答的启发,这个包含 Perl 单行代码的命令可以做到这一点:

find ./ -name \*.txt | perl -p -e 's/^(.*\/(.*)\.txt)$/mv $1 .\/foo\/$2.bar.txt/' | bash

【讨论】:

以上是关于修改 xargs 中的替换字符串的主要内容,如果未能解决你的问题,请参考以下文章

Linux下使用xargs将多行文本转换成一行并用tr实现逗号隔开

linux 文本内容替换(awk/sed)

Python 替换列表 list 及字符串中的元素

递归查找和替换字符串

rename 如何批量替换文件名中的字符?

grep 或 find 和 sed 替换字符串