【问题标题】:How to go to each directory and execute a command?如何去每个目录执行一个命令?
【发布时间】:2011-11-20 04:10:49
【问题描述】:

如何编写一个 bash 脚本,该脚本遍历 parent_directory 中的每个目录并执行 每个目录中的命令。

目录结构如下:

parent_directory(名称可以是任何东西 - 不遵循模式)

  • 001(目录名称遵循此模式)
    • 0001.txt(文件名遵循此模式)
    • 0002.txt
    • 0003.txt
  • 002
    • 0001.txt
    • 0002.txt
    • 0003.txt
    • 0004.txt
  • 003
    • 0001.txt

目录数量未知。

【问题讨论】:

    标签: bash shell unix find


    【解决方案1】:

    Todd 发布的这个 answer 帮助了我。

    find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
    

    \( ! -name . \) 避免在当前目录执行命令。

    【讨论】:

    • 如果你只想在某些文件夹中运行命令:find FOLDER* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
    • 我不得不做-exec bash -c 'cd "$0" && pwd' {} \;,因为一些目录包含单引号和一些双引号。
    • 你也可以通过添加-mindepth 1省略当前目录
    • 如何将其更改为函数接受类似“git remote get-url origin`而不是直接pwd的命令?
    • disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;} 不起作用。
    【解决方案2】:

    当您的当前目录为parent_directory时,您可以执行以下操作:

    for d in [0-9][0-9][0-9]
    do
        ( cd "$d" && your-command-here )
    done
    

    ( 和) 创建了一个子shell,因此在主脚本中当前目录不会改变。

    【讨论】:

    • 这里没关系,因为通配符只匹配数字,但在一般情况下,您基本上总是希望将目录名称放在循环内的双引号中。 cd "$d" 会更好,因为它会转移到通配符匹配名称包含空格和/或 shell 元字符的文件的情况。
    • (这里的所有答案似乎都有相同的缺陷,但在投票最多的答案中最重要。)
    • 此外,绝大多数命令实际上并不关心您在哪个目录中执行它们。 for f in foo bar; do cat "$f"/*.txt; done >output 在功能上等同于 cat foo/*.txt bar/*.txt >output。但是,ls 是一个命令,它会根据您传递参数的方式产生略微不同的输出;同样,如果您从子目录运行它,输出相对文件名的grep 或wc 会有所不同(但通常,您希望避免进入子目录正是出于这个原因)。
    【解决方案3】:

    如果你使用 GNU find,你可以试试-execdir 参数,例如:

    find . -type d -execdir realpath "{}" ';'
    

    或(根据@gniourf_gniourf comment):

    find . -type d -execdir sh -c 'printf "%s/%s\n" "$PWD" "$0"' {} \;
    

    注意:你可以使用${0#./}而不是$0来修复前面的./。

    或更实际的例子:

    find . -name .git -type d -execdir git pull -v ';'
    

    如果你想包含当前目录,使用-exec会更简单:

    find . -type d -exec sh -c 'cd -P -- "{}" && pwd -P' \;
    

    或使用xargs:

    find . -type d -print0 | xargs -0 -L1 sh -c 'cd "$0" && pwd && echo Do stuff'
    

    或@gniourf_gniourf建议的类似示例:

    find . -type d -print0 | while IFS= read -r -d '' file; do
    # ...
    done
    

    以上示例支持名称中带有空格的目录。


    或者通过分配到 bash 数组中:

    dirs=($(find . -type d))
    for dir in "${dirs[@]}"; do
      cd "$dir"
      echo $PWD
    done
    

    将. 更改为您的特定文件夹名称。如果您不需要递归运行,则可以使用:dirs=(*)。上面的示例不支持名称中带有空格的目录。

    因此,正如@gniourf_gniourf 所建议的那样,在不使用显式循环的情况下将 find 的输出放入数组中的唯一正确方法将在 Bash 4.4 中提供:

    mapfile -t -d '' dirs < <(find . -type d -print0)
    

    或者不推荐的方式(涉及parsing of ls):

    ls -d */ | awk '{print $NF}' | xargs -n1 sh -c 'cd $0 && pwd && echo Do stuff'
    

    上面的例子会忽略当前的目录(根据 OP 的要求),但它会用空格中断名称。

    另见:

    【讨论】:

    • 在不使用显式循环的情况下将find 的输出放入数组中的唯一正确方法将在带有mapfile -t -d '' dirs &lt; &lt;(find . -type d -print0) 的Bash 4.4 中可用。在此之前,您唯一的选择是使用循环(或将操作推迟到find)。
    • 其实你并不需要dirs数组;你可以像这样循环find的输出(安全地):find . -type d -print0 | while IFS= read -r -d '' file; do ...; done。
    • @gniourf_gniourf 我很直观,总是试图找到/制作看起来简单的东西。例如。我有this script 并且使用 bash 数组对我来说很容易且可读(通过将逻辑分成更小的部分,而不是使用管道)。引入额外的管道,IFS,read,它给人的印象是额外的复杂性。我得考虑一下,也许有更简单的方法。
    • 如果 easy 你的意思是 broken 那么是的,有更简单的方法。现在不用担心,所有这些 IFS 和 read(如您所说)在您习惯它们时会成为第二天性……它们是编写脚本的规范和惯用方式的一部分。
    • 顺便说一句,你的第一个命令 find . -type d -execdir echo $(pwd)/{} ';' 没有做你想做的事情($(pwd) 在 find 甚至被执行之前被扩展)......
    【解决方案4】:

    您可以通过管道然后使用xargs 来实现此目的。问题是您需要使用-I 标志,它将用每个xargs 传递的子字符串替换您的bash 命令中的子字符串。

    ls -d */ | xargs -I {} bash -c "cd '{}' && pwd"
    

    你可能想用你想在每个目录中执行的任何命令替换pwd。

    【讨论】:

    • 我不知道为什么这没有被赞成,但我找到了迄今为​​止最简单的脚本。谢谢
    【解决方案5】:

    如果知道顶层文件夹,你可以这样写:

    for dir in `ls $YOUR_TOP_LEVEL_FOLDER`;
    do
        for subdir in `ls $YOUR_TOP_LEVEL_FOLDER/$dir`;
        do
          $(PLAY AS MUCH AS YOU WANT);
        done
    done
    

    在 $(随心所欲地玩);你可以放尽可能多的代码。

    请注意,我没有在任何目录上“cd”。

    干杯,

    【讨论】:

    • 这里有几个"Useless Use of ls" 的实例——你可以改用for dir in $YOUR_TOP_LEVEL_FOLDER/*。
    • 好吧,也许它在一般意义上没有用,但它允许直接在 ls 中进行过滤(即所有以 .tmp 结尾的目录)。这就是我使用ls $dir 表达式的原因
    • for subdir in 'ls $YOUR_TOP_LEVEL_FOLDER/$dir';会像parent/child1/child1_1/child1_1_1/一样遍历嵌套的多个目录吗?或者只是父目录深处的一个目录?
    【解决方案6】:
    for dir in PARENT/*
    do
      test -d "$dir" || continue
      # Do something with $dir...
    done
    

    【讨论】:

    • 这很容易理解!
    【解决方案7】:

    虽然一个衬里有利于快速和肮脏的使用,但我更喜欢下面更详细的版本来编写脚本。这是我使用的模板,它处理了许多边缘情况,并允许您编写更复杂的代码以在文件夹上执行。您可以在函数 dir_command 中编写 bash 代码。下面以 dir_coomand 实现在 git 中标记每个仓库为例。脚本的其余部分为目录中的每个文件夹调用 dir_command。还包括仅遍历给定文件夹集的示例。

    #!/bin/bash
    
    #Use set -x if you want to echo each command while getting executed
    #set -x
    
    #Save current directory so we can restore it later
    cur=$PWD
    #Save command line arguments so functions can access it
    args=("$@")
    
    #Put your code in this function
    #To access command line arguments use syntax ${args[1]} etc
    function dir_command {
        #This example command implements doing git status for folder
        cd $1
        echo "$(tput setaf 2)$1$(tput sgr 0)"
        git tag -a ${args[0]} -m "${args[1]}"
        git push --tags
        cd ..
    }
    
    #This loop will go to each immediate child and execute dir_command
    find . -maxdepth 1 -type d \( ! -name . \) | while read dir; do
       dir_command "$dir/"
    done
    
    #This example loop only loops through give set of folders    
    declare -a dirs=("dir1" "dir2" "dir3")
    for dir in "${dirs[@]}"; do
        dir_command "$dir/"
    done
    
    #Restore the folder
    cd "$cur"
    

    【讨论】:

      【解决方案8】:

      我不明白文件的格式,因为您只想遍历文件夹...您在寻找这样的东西吗?

      cd parent
      find . -type d | while read d; do
         ls $d/
      done
      

      【讨论】:

      • 你应该在 find 中添加-maxdepth 1 -mindepth 1 否则它也会获取所有子目录!
      【解决方案9】:

      你可以使用

      find .
      

      递归搜索当前目录中的所有文件/目录

      你可以像这样通过管道输出 xargs 命令

      find . | xargs 'command here'
      

      【讨论】:

      • 这不是被问到的。
      【解决方案10】:
        #!/bin.bash
      for folder_to_go in $(find . -mindepth 1 -maxdepth 1 -type d \( -name "*" \) ) ; 
                                          # you can add pattern insted of * , here it goes to any folder 
                                          #-mindepth / maxdepth 1 means one folder depth   
      do
      cd $folder_to_go
        echo $folder_to_go "########################################## "
        
        whatever you want to do is here
      
      cd ../ # if maxdepth/mindepath = 2,  cd ../../
      done
      
      #you can try adding many internal for loops with many patterns, this will sneak anywhere you want
      

      【讨论】:

        【解决方案11】:
        for p in [0-9][0-9][0-9];do
            (
                cd $p
                for f in [0-9][0-9][0-9][0-9]*.txt;do
                    ls $f; # Your operands
                done
            )
        done
        

        【讨论】:

        • 你需要重新备份目录,或者在子shell中执行cd。
        • 投反对票:编辑丢失了cd,所以这段代码实际上并没有做任何有用的事情。
        【解决方案12】:

        您可以在每个文件夹中的 1 行中运行一系列命令,例如:

        for d in PARENT_FOLDER/*; do (cd "$d" && tar -cvzf $d.tar.gz *.*)); done
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-11-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多