如何去每个目录执行一个命令?
Posted
技术标签:
【中文标题】如何去每个目录执行一个命令?【英文标题】: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目录数量未知。
【问题讨论】:
【参考方案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 的要求),但它会用空格中断名称。
另见:
Bash: for each directory 在 SO How to enter every directory in current path and execute script? 在 SE Ubuntu【讨论】:
在不使用显式循环的情况下将find
的输出放入数组中的唯一正确方法将在带有mapfile -t -d '' dirs < <(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 ofls
" 的实例——你可以改用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
【讨论】:
以上是关于如何去每个目录执行一个命令?的主要内容,如果未能解决你的问题,请参考以下文章