我很好奇,是否有一个函数/命令可以应对循环管道?
很容易出现需求不足的情况
- 你运行命令返回多行
- 你需要处理每一行
- 将每一行传递给另一个函数/命令
例如:
您需要找到一些与模式匹配的路径,然后将其移动到另一个位置:
# 1.
find path_A -name '*AAA*' -exec mv -t path_B {} +
# 2.
find path_A -name "*AAA*" -print0 | xargs -0 -I {} mv {} path_B
人们会感到困惑,
- 为什么需要 mv
-t
(我的意思是,通过一个 [for 循环管道] 你可以使用mv {} some_path
,就像你没有管道所写的一样) - 为什么 exec 或 | xargs 会这样写,xargs 需要更多的选项,但其实在很多计算机语言中是不需要的。
- 为什么我不能使用
ls ... mv -t path_B {} +
,或者ls | xargs -0 -I {} mv {} path_B
换句话说,为什么我换到另一个命令后它不起作用? - 为什么我改为
mv
:7z
会 失败find path_A -name '*AAA*' -exec 7z -t {} +
?
注意:以上不是问题,只是例子!!
我问这个问题是为了寻找一种常见且简单的方法来应对循环管道。有没有一种简单而常见的方法可以做到:
# 0
command with option (usually you use a command like this)
# 1
command with option [for loop pipe] command with option | command with option
# 2
command with option [for loop pipe] command with option | command with option [for loop pipe] command with option
# 3
....
因此,当你使用 [for loop pipe] 时不需要进行任何更改command with option
。这对于日常使用来说会方便很多。
更新
我举出所有的例子只是想说明,当不同的命令有自己的风格来应对多行输入时,正常的方式会让人困惑,它需要exec
或xargs
其他一些选项。如果我们有一个for pipe
命令可以使用选项连接两个命令。这样使用起来就方便多了
我的意思是,如果你使用一种高级语言。例如 Python,
def pipe(x, func, *func_options):
return func(x, *func_options)
def forloop_pipe(x, func, *func_options):
return [func(i, *func_options) for i in x.split('\n')]
def ls(*options)
pass
def mv(*options)
pass
a = ls('*.txt')
b = forloop_pipe(a, mv, '/home')
这是一个非常简单的例子,有很多缺陷,但它解释了我在 bash 命令中尝试获得的内容。
答案1
我不确定这是否是对该问题的直接回答(有多个问题),但我认为您问的是类似这样的问题:
for
循环命令替换或者while
循环流程替代。
for
循环$(command substitution)
命令替换允许命令的输出替换命令本身。我们可以for
用这种方式将此功能与循环结合起来:
for item in "$(find . -type f)"
do
echo "$item" | tee -a ./"file-list.txt"
done
while
循环<(process substitution)
工艺替代允许使用文件名引用进程的输入或输出。我们可以while
借助内置的 ,将此功能与循环结合起来read
,如下所示:
while IFS= read -r item
do
echo "$item" | tee -a ./"file-list.txt"
done < <(find . -type f)
在最后一部分中< <(find . -type f)
,第一个<
表示 while 的 stdin loop
,<(find . -type f)
将被视为文件。有关构造的更多信息,IFS= read -r line
请参阅这篇很棒的文章斯蒂芬·查泽拉斯 (Stéphane Chazelas) 的作品。
您可以使用管道代替来实现与上述相同的效果流程替代(但不建议采用这种方式,因为它可能会导致错误):
find . -type f | while IFS= read -r file
do
echo "$file" | tee -a ./"file-list.txt"
done
将循环的输出通过管道传输到另一个命令
此外,我们可以将循环的输出通过管道或重定向到另一个命令或函数:
while IFS= read -r -d '' item; do
# Compose the name of the new file
DIR="$(dirname "${item}")"
FILE_NAME="$(basename "${item}")"
NEW_FILE_NAME="new-${FILE_NAME}"
# move the file and suppress the potential output
# of the 'mv' command by redirecting it to a log file
mv "${item}" "${DIR}/${NEW_FILE_NAME}" >>/tmp/mv-loop.log 2>&1
# output the name of the new file in order to be processed
# by the next (piped) command
printf '%b' "${DIR}/${NEW_FILE_NAME}"'\0'
done < <(find . -type f -print0) | xargs -0 -I{} 7z a -t7z "the_archive_name.7z" {}
请注意,在上面的例子中,到处都使用了空分隔符:
find . -print0
,IFS= read -r -d '' item
-参考,printf '%b' a'\0'b
-参考,xargs -0
(在这种情况下我们实际上不需要命令中的-I{}
and )。{}
xargs
此示例将递归移动(重命名)所有文件,但在存档中目录结构不会出现。
答案2
有没有一个简单而常见的方法来做
答案:不,每个命令都是不同的。
关于您的问题:
您不需要
-t
,但随后mv
将为每个文件运行,这会慢很多。您需要以而不是来结束您的-exec
命令。\;
+
管道与循环无关
for
,管道发送标准输出从管道左侧到标准输入在右侧。xargs
读取自标准输入(或来自带有选项的文件-a
)并使用从输入中获取的参数运行命令。ls
没有-exec
选择。ls
将要可以使用xargs
,但不能使用xargs -0
becausels
没有用于 NULL 分隔输出的选项。这也是为什么ls
你根本不应该使用管道(文件名可以有换行符)。对于
mv
,-t
是目标目录,而7z
对于档案类型,您为什么认为它应该有效?
在我看来,你使用的命令似乎你并不真正理解。这对任何编程或脚本语言都不起作用。
顺便说一句:您应该使用xargs
选项-r
来避免空输入的问题。
答案3
也许你的答案是xargs -n 1
看这个例子:仅 1 行(为 gzip 生成循环):
ls | grep -i '.csv' | xargs -n 1 gzip
或者
ls | grep -i '.gz' | xargs -n 1 gunzip