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

fus*_*y89 2 linux bash sed sh

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

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

mytest:
    a:
        1.txt
        2.txt
        3.txt
    b:
        4.txt
        5.txt
Run Code Online (Sandbox Code Playgroud)

我试图使用的命令是:

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/* .'
Run Code Online (Sandbox Code Playgroud)

不幸的是我收到一个错误:

mv: cannot stat './aaa/*': No such file or directory
Run Code Online (Sandbox Code Playgroud)

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

for dir in */; do
  ls $dir;
done;
Run Code Online (Sandbox Code Playgroud)

即使我使用sed用'\'替换每个目录名中的空格,或用引号括起目录名,我也会得到同样的错误.

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

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

mkl*_*nt0 5

更新:完全重写.

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

一种有效find的解决方案

一般来说,如果可行的话,让find自己完成工作是最好和最有效的,而不需要额外的工具; find-exec动作就像一个内置的xargs,{}表示手头的路径(带终结符\;)/ 所有路径(带+):

find . -type f -exec echo mv -t . {} +
Run Code Online (Sandbox Code Playgroud)

为了安全起见,他将只打印mv 命令被执行; 删除echo实际执行它们.

这将执行所有匹配文件传递到的单个[1] mv命令,并将它们全部移动到当前目录.-t .

[1]如果生成的命令行太长(不太可能),它会被分成多个命令,就像使用xargs.

文件(-type f)进行操作会绕过对globbing的需要,find然后会为您枚举所有文件(它也会绕过.显式排除的需要).

请注意,此解决方案适用于整个子树,而不仅仅是(立即)子目录.
考虑打开Bash 4的globstar选项和使用是很诱人的mv */** .,但这不会起作用,因为它也会尝试移动目录,而不仅仅是文件中的文件.

一个再警告-exec+:它仅适用{}-占位符的所有路径-是令牌之前,立即+.

既然你是在Linux上,我们可以通过指定目标文件夹满足这个条件mv选项 -t {}; 在基于BSD的系统(如OSX)上,你不能这样做,因为mv它不支持-t,所以你必须使用终结符\;,这意味着每个路径mv都会调用一次,这显然要慢得多.


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

正如@EtanReisner在注释中指出的那样,xargs调用没有(隐式)涉及shell的指定命令,因此globbing将不起作用 ; 您可以使用以下命令进行验证:

echo '*' | xargs echo  # -> '*' - NO globbing
Run Code Online (Sandbox Code Playgroud)

如果我们将globbing问题放在一边,那么xargs使用嵌入空格(或其他shell元字符)的文件夹名称使命令正常工作将需要额外的工作:

 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
Run Code Online (Sandbox Code Playgroud)

注意(组合)sed命令现在如何生成单个输出行'<input-path>'/* .,输入路径用嵌入的单引号括起来,即使它包含嵌入空格也需要将xargs其识别<input-path>为单个参数.
(如果你的文件名包含单引号,你必须做更多的工作;还要注意,由于现在对于一个给定的目录中的所有参数都在.单一的线,你可以使用xargs -L 1 ....)

还要注意如何-mindepth 1(仅使用子目录级别或更低级别的进程路径)跳过.自身的处理.

使globbing发生的唯一方法是让shell涉及:

 find . -mindepth 1 -type d | 
    sed -r "s/.*/'&'\/* ./" | # -> '<input-path>'/* . (including single-quotes)
         xargs -I {} sh -c 'echo mv {}' # works, but is inefficient
Run Code Online (Sandbox Code Playgroud)

注意使用xargs' -I选项将每个输入行视为自己的参数({}是输入的自选占位符).

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

但总的来说,这是非常低效的:

  • 使用具有3个段的管道.
  • 每个输入路径调用一个shell实例,然后调用该mv实用程序.

将此与上述有效的find解决方案进行比较,该解决方案(通常)总共只创建2个进程.