Estou trabalhando em um script que precisa executar uma ação em todos os subdiretórios de uma pasta específica.
Qual é a maneira mais eficiente de escrever isso?
bash
command
directory-traversal
mikewilliamson
fonte
fonte
mkdir 'foo * bar'
causaráfoo
ebar
será repetida mesmo que eles não existem e*
serão substituídos por uma lista de todos os nomes de arquivos, mesmo os que não sejam do diretório).mkdir -p '/tmp/ /etc/passwd /'
- se alguém executa um script seguindo esta prática/tmp
para, por exemplo, encontrar diretórios para excluir, eles podem acabar excluindo/etc/passwd
.Respostas:
fonte
find
parâmetros se desejar um comportamento recursivo ou não recursivo.find . -mindepth 1 -type d
mkdir 'directory name with spaces'
em quatro palavras separadas.-mindepth 1 -maxdepth 1
ou foi muito profundo.Uma versão que evita a criação de um subprocesso:
Ou, se sua ação for um único comando, isso é mais conciso:
Ou uma versão ainda mais concisa ( obrigado @enzotib ). Observe que nesta versão cada valor de
D
terá uma barra final:fonte
if
ou[
com:for D in */; do
for D in *; do [ -d "${D}" ] && my_command; done
ou uma combinação dos dois mais recentes:for D in */; do [ -d $D ] && my_command; done
for D in .* *; do
vezfor D in *; do
.A maneira mais simples e não recursiva é:
No
/
final, use apenas diretórios.Não há necessidade de
fonte
shopt -s dotglob
para incluir arquivos de pontos / pontos de ponto ao expandir curingas. Veja também: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html/*
, em vez de*/
com o/
que representa o caminho que você deseja usar./*
seria para caminho absoluto enquanto que*/
incluiria os subdiretórios do local atual${d%/*}
Usar
find
comandoNo GNU
find
, você pode usar o-execdir
parâmetro:ou usando o
-exec
parâmetro:ou com o
xargs
comando:Ou usando o loop for :
Para recursividade, tente estender o globbing (
**/
) em vez disso (ativar por :)shopt -s extglob
.Para mais exemplos, consulte: Como ir para cada diretório e executar um comando? na SO
fonte
-exec {} +
é especificado para POSIX,-exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +
é outra opção legal e chama menos shells que-exec sh -c '...' {} \;
.Handy one-liners
A opção A está correta para pastas com espaços no meio. Além disso, geralmente mais rápido, pois não imprime cada palavra no nome de uma pasta como uma entidade separada.
fonte
find . -type d -print0 | xargs -0 -n 1 my_command
fonte
my_command
.Isso criará um subshell (o que significa que os valores das variáveis serão perdidos quando o
while
loop sair):Isso não vai:
Qualquer um funcionará se houver espaços nos nomes de diretório.
fonte
find ... -print0
ewhile IFS="" read -r -d $'\000' dir
-d ''
é menos enganador sobre sintaxe e recursos do bash, pois-d $'\000'
implica (falsamente) que$'\000'
é de alguma forma diferente de''
- de fato, alguém poderia facilmente (e novamente, falsamente) inferir de o bash suporta seqüências de caracteres no estilo Pascal (especificadas em comprimento, capazes de conter literais NUL) em vez de seqüências de caracteres C (delimitadas por NUL, incapazes de conter NULs).Você poderia tentar:
ou similar...
Explicação:
find . -maxdepth 1 -mindepth 1 -type d
: Encontre apenas diretórios com profundidade recursiva máxima de 1 (apenas os subdiretórios $ 1) e profundidade mínima de 1 (excluindo a pasta atual.
)fonte
cd "$f"
. Ele não funciona quando a saída defind
é dividida em seqüências de caracteres, portanto, você terá as partes separadas do nome como valores separados$f
, tornando o quão bem você faz ou não cita$f
o debate de expansão.find
A saída de é orientada a linhas (um nome para uma linha, em teoria - mas veja abaixo) com a-print
ação padrão .find -print
não é uma maneira segura de passar nomes de arquivos arbitrários, pois é possível executar algomkdir -p $'foo\n/etc/passwd\nbar'
e obter um diretório que possui/etc/passwd
uma linha separada em seu nome. O tratamento de nomes de arquivos/upload
ou/tmp
diretórios sem cuidado é uma ótima maneira de obter ataques de escalonamento de privilégios.a resposta aceita será interrompida em espaços em branco se os nomes de diretório os tiverem, e a sintaxe preferida é
$()
para bash / ksh. Use aGNU find
-exec
opção com+;
por exemplofind .... -exec mycommand +;
#this is same as passing to xargs
ou use um loop while
fonte
find -exec
e a passagem paraxargs
:find
ignorará o valor de saída do comando que está sendo executado, enquantoxargs
falhará em uma saída diferente de zero. Pode estar correto, dependendo de suas necessidades.find ... -print0 | while IFS= read -r d
é mais seguro - suporta nomes que começam ou terminam em espaço em branco e nomes que contêm literais de nova linha.