管道时没有这样的文件或目录。每个命令单独工作,但在管道

时间:2015-10-14 00:58:49

标签: linux bash sed sh

我有2个文件夹:folder_a& folder_b。在每个文件夹中都有一堆文件。我正在尝试使用sed将所有这些文件从这些文件夹中移出,并进入我当前所在的工作目录。

我的文件夹结构如下所示:

mytest:
    a:
        1.txt
        2.txt
        3.txt
    b:
        4.txt
        5.txt

我尝试使用的命令是:

find . -type d ! -iname '*.*'  # find all folders other than root
    | sed -r 's/.*/&\/*/'      # add '/*' to each of the arguments
    | sed -r 'p;s/.*/./'       # output: a/* . b/* .
    | xargs -n 2 mv            # should be creating two commands: 'mv a/* .' and 'mv b/* .'

不幸的是我收到了错误:

mv: cannot stat './aaa/*': No such file or directory

当我尝试其他策略时(使用ls而不是mv),我也会得到同样的错误:

for dir in */; do
  ls $dir;
done;

即使我使用sed将每个目录名称中的空格替换为' \',或者用引号括住目录名称,我也会收到同样的错误。

我不确定这两个例子是否与我对bash的误解有关,但它们似乎都表明我对bash如何将一个命令的输出转换为另一个命令的输入的无知。

任何人都可以对此有所了解吗?

1 个答案:

答案 0 :(得分:5)

更新:完全重写。

正如@EtanReisner和@melpomene所指出的那样, mv */* .或更具体地说,mv a/* b/* .是最直接的解决方案,但是你说这部分是学习练习,所以答案的其余部分显示了一个有效的基于find的解决方案,并解释了原始命令的问题。

基于find的高效解决方案

通常,如果可行的话,让find本身完成工作是最好和最有效的,而不需要额外的工具; find的{​​{1}}操作就像内置的-exec一样,xargs代表手头的路径(终结者{})/ 全部路径(\;):

+

为了安全起见,他只需打印 执行的find . -type f -exec echo mv -t . {} + 命令;删除mv以实际执行它们。 功能

这将执行 [1] echo命令,所有匹配文件将传递到该命令,{{1将它们全部移动到当前目录。

[1]如果生成的命令行太长(不太可能),它会被分成多个命令,就像mv一样。

文件进行操作-t .)会绕过对globbing的需求,因为xargs将为您枚举所有文件(它还绕过了排除{{1}的需要显式)。

请注意,此解决方案适用于整个子树,而不仅仅是(立即)子目录。
考虑打开Bash 4的-type f选项并使用find是很诱人的,但这不会起作用,因为它也会尝试移动目录,而不是只是其中的文件。

警告. globstar :仅当mv */** . - 所有路径的占位符 - 是之前的标记<<}时才有效/ em> -exec

由于您使用的是Linux,我们可以通过在选项 + 之前为{}指定目标文件夹来满足此条件 +;在基于BSD的系统(如OSX)上,您可以这样做,因为mv不支持-t,因此您必须使用终结符{{1 }},这意味着{}每个路径都被称为一次,这显然要慢得多。

为什么你的命令不起作用:

正如@EtanReisner在评论中指出的那样, mv调用没有(隐式)涉及shell的指定命令,因此globbing不起作用;您可以使用以下命令验证这一点:

-t

如果我们不考虑通配问题,则需要进行额外的工作以使\;命令与包含嵌入空格(或其他shell元字符)的文件夹名称一起正常工作:

mv

注意(组合的)xargs命令现在如何生成单个输出行echo '*' | xargs echo # -> '*' - NO globbing ,输入路径包含在 embedded 中引号,xargs find . -mindepth 1 -type d | sed -r "s/.*/'&'\/* ./" | # -> '<input-path>'/* . (including single-quotes) xargs -n 2 echo mv # NOTE: still won't work due to lack of globbing 识别为单个参数所必需的引号,即使它包含嵌入的空格。
(如果你的文件名包含单引号,你必须做更多的工作;还要注意,既然现在所有的参数都在行上,你可以使用{{1} }。)

另请注意sed(仅在子目录级别或以下级别的处理路径)用于跳过'<input-path>'/* .本身的处理。

使全局发生的唯一方法是让shell参与

xargs

请注意,使用<input-path>'xargs -L 1 ...选项将每个输入行视为自己的参数(-mindepth 1是输入的自选占位符。)

.调用(默认)shell来执行生成的命令,在该命令处发生globbing。

但总的来说,效率非常低:

  • 使用包含3个段的管道。
  • 每个输入路径调用一个shell实例,该路径又调用 find . -mindepth 1 -type d | sed -r "s/.*/'&'\/* ./" | # -> '<input-path>'/* . (including single-quotes) xargs -I {} sh -c 'echo mv {}' # works, but is inefficient 实用程序。

将此与上面的高效xargs解决方案进行比较,该解决方案(通常)仅创建 2 进程