如何进入每个目录并执行命令?


143

我怎样写一个bash脚本,通过每一个目录使用一个parent_directory内执行一个命令每个目录

目录结构如下:

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

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

目录数未知。

Answers:


108

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

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

()创建一个子shell,所以当前目录没有在主脚本改变。


5
此处无关紧要,因为通配符除数字外不匹配任何其他内容,但在一般情况下,您基本上总是希望将目录名称放在循环中的双引号中。 cd "$d"这样会更好,因为它可以转移到通配符确实匹配名称包含空格和/或外壳元字符的文件的情况。
2015年

1
(这里的所有答案似乎都具有相同的缺陷,但在投票最多的答案中最重要。)
Tripleee 2015年

1
另外,绝大多数命令实际上并不关心您在哪个目录中执行它们。 for f in foo bar; do cat "$f"/*.txt; done >output在功能上等效于cat foo/*.txt bar/*.txt >output。但是,ls根据您传递参数的方式,它是一个会产生稍微不同的输出的命令;同样地,grep或者wc其输出,如果你从一个子目录运行它(但通常,要避免因为这个原因进入一个子目录精确)相对文件名会有所不同。
2015年

158

这个答案张贴由托德帮我。

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

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


4
而且,如果您只想在某些文件夹中运行命令:find FOLDER* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
Martin K

3
我必须这样做,-exec bash -c 'cd "$0" && pwd' {} \;因为某些目录包含单引号和一些双引号。
Charlie Gorichanaz

12
您还可以通过添加-mindepth 1
mdziob

如何将其更改为接受“ git remote get-url origin`之类的命令而不是pwd直接接受的命令?”
roachsinai

disd(){find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c 'cd "{}" && "$@"' \;}不行。
roachsinai

48

如果您使用的是GNU find,则可以尝试使用-execdir参数,例如:

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

或(根据@gniourf_gniourf注释):

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)

还是不建议使用的方法(涉及对的解析ls):

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

上面的示例将忽略当前目录(按OP的要求),但是它将破坏带有空格的名称。

也可以看看:


1
find在不使用显式循环的情况下将数组的输出放入数组的唯一正确方法将在Bash 4.4中使用mapfile -t -d '' dirs < <(find . -type d -print0)。在此之前,您唯一的选择是使用循环(或将操作推迟到find)。
gniourf_gniourf 2015年

1
实际上,您实际上并不需要dirs数组。你可以在回路find的输出(安全),像这样:find . -type d -print0 | while IFS= read -r -d '' file; do ...; done
gniourf_gniourf 2015年

@gniourf_gniourf我很形象,总是试图寻找/使看起来简单的东西。例如,我有这个脚本,使用bash数组对我来说很容易理解(通过将逻辑分成较小的部分,而不是使用管道)。引入额外的管道,IFSread,它提供了额外的复杂性的印象。我必须考虑一下,也许有一些更简单的方法可以做到。
kenorb 2015年

如果您更容易理解破碎,那么是的,可以采用更简单的方法。现在不用担心,当您习惯它们时,所有这些(IFS并且read如您所说)已成为第二天性……它们是编写脚本的规范和惯用方式的一部分。
gniourf_gniourf 2015年

顺便说一句,您的第一个命令find . -type d -execdir echo $(pwd)/{} ';'没有执行您想要的操作(在执行$(pwd)之前find已展开)…
gniourf_gniourf 2015年

29

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

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

您可能要替换pwd为要在每个目录中执行的任何命令。


2
我不知道为什么还没有解决这个问题,但是我找到了迄今为​​止最简单的脚本。谢谢
Linvi '19

20

如果顶层文件夹是已知的,则可以这样写:

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”。

干杯,


3
这里有几个“无用的ls实例-您可以改for dir in $YOUR_TOP_LEVEL_FOLDER/*而做。
马克·隆air11年

1
好吧,也许它在一般意义上是无用的,但是它允许直接在ls中进行过滤(即,所有以.tmp结尾的目录)。这就是为什么我使用ls $dir表达式
gforcada11年

13
for dir in PARENT/*
do
  test -d "$dir" || continue
  # Do something with $dir...
done

这很容易理解!
Rbjz

6

尽管一个内衬适合快速,肮脏的用法,但我更喜欢在下面编写脚本时使用更详细的版本。这是我使用的模板,可以处理许多极端情况,并允许您编写更复杂的代码以在文件夹上执行。您可以在函数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"

5

我不了解文件的格式,因为您只想遍历文件夹...您是否正在寻找类似的东西?

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


0
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

您需要再次更改备份目录,或者cd在子shell中进行操作。
Mark Longair 2011年

Downvote:编辑丢失了,cd因此此代码实际上没有做任何有用的事情。
Tripleee 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.