Capturando saída do achado. -print0 em uma matriz de festa
Pergunta
Usando find . -print0
parece ser a única maneira segura de obter uma lista de arquivos em festa devido à possibilidade de nomes de arquivos que contenham espaços, novas linhas, aspas, etc.
No entanto, estou tendo um momento difícil realmente fazer a saída do achado útil dentro bash ou com outros utilitários de linha de comando. A única maneira que eu consegui fazer uso da saída é, canalizando-o para perl, e mudando IFS do Perl como nulo:
find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;'
Este exemplo imprime o número de arquivos encontrados, evitando o perigo de novas linhas em nomes de arquivos corrompendo a contagem, como ocorreria com:
find . | wc -l
Como os programas de linha mais comandos não suportam entrada delimitada por nulo, eu acho que a melhor coisa seria a de capturar a saída de find . -print0
em uma matriz bash, como eu fiz nos perl trecho acima, e, em seguida, continuar com a tarefa , seja ela qual for.
Como posso fazer isso?
Isso não funciona:
find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} )
Um muito mais geral pergunta pode ser:? Como posso fazer coisas úteis com listas de arquivos em bash
Solução
Descaradamente roubado href="http://mywiki.wooledge.org/BashFAQ/020" rel="noreferrer"> de Greg:
unset a i
while IFS= read -r -d $'\0' file; do
a[i++]="$file" # or however you want to process each file
done < <(find /tmp -type f -print0)
Note que a construção redirecionamento usado aqui (cmd1 < <(cmd2)
) é semelhante, mas não exatamente o mesmo que o oleoduto mais usual (cmd2 | cmd1
) - se os comandos são builtins shell (por exemplo while
), o gasoduto versão executa-as em subshells , e todas as variáveis ??que definidos (por exemplo, o a
matriz) são perdidos quando eles saem. cmd1 < <(cmd2)
só funciona cmd2 em um subnível, então as vidas de matriz últimos a sua construção. Aviso: esta forma de redirecionamento está disponível apenas em bash, nem mesmo bater no modo sh-emulação; você deve começar o seu roteiro com #!/bin/bash
.
Além disso, porque a etapa de processamento do arquivo (neste caso, apenas a[i++]="$file"
, mas você pode querer fazer algo amador diretamente no circuito) tem a sua entrada redirecionada, não pode usar quaisquer comandos que pode ler de stdin. Para evitar esta limitação, que tendem a usar:
unset a i
while IFS= read -r -u3 -d $'\0' file; do
a[i++]="$file" # or however you want to process each file
done 3< <(find /tmp -type f -print0)
... que passa a lista de arquivos através da unidade 3, em vez de stdin.
Outras dicas
Talvez você está procurando xargs:
find . -print0 | xargs -r0 do_something_useful
A opção -L 1 poderia ser útil para você também, o que torna xargs exec do_something_useful com o argumento de apenas 1 arquivo.
O principal problema é que o NUL delimitador (\ 0) é inútil aqui, porque não é possível atribuir IFS um NUL valor. Assim como bons programadores tomarmos cuidado, que a entrada para o nosso programa é algo que é capaz de lidar.
Primeiro vamos criar um pequeno programa, que faz esta parte para nós:
#!/bin/bash
printf "%s" "$@" | base64
... e chamá-lo base64str (não se esqueça chmod + x)
Em segundo lugar, podemos agora usar uma simples e direta para-loop:
for i in `find -type f -exec base64str '{}' \;`
do
file="`echo -n "$i" | base64 -d`"
# do something with file
done
Portanto, o truque é, que uma base64-string não tem nenhum sinal que causa problemas para a festança -., Naturalmente, um xxd ou algo lata semelhante também fazer o trabalho
No entanto, uma outra forma de arquivos de contagem:
find /DIR -type f -print0 | tr -dc '\0' | wc -c
Desde Bash 4.4, o mapfile
builtin tem o interruptor -d
(para especificar um delimitador, similar ao interruptor -d
da declaração read
), e o delimitador pode ser o byte nulo. Assim, uma resposta boa para a pergunta no título
Captura de saída de
find . -print0
em uma matriz de festa ??p>
é:
mapfile -d '' ary < <(find . -print0)
Você pode seguramente fazer a contagem com este:
find . -exec echo ';' | wc -l
(Imprime uma nova linha para cada arquivo / dir encontrado, e depois contar as novas linhas impressas ...)
Eu acho que soluções mais elegantes existe, mas eu vou lançar este nesta trabalhará igualmente para nomes de arquivos com espaços e / ou novas linhas:.
i=0;
for f in *; do
array[$i]="$f"
((i++))
done
Você, então, pode por exemplo listar os arquivos um por um (neste caso, em ordem inversa):
for ((i = $i - 1; i >= 0; i--)); do
ls -al "${array[$i]}"
done
Esta página dá um exemplo bom, e para mais consulte Capítulo 26 na Guia Avançado Bash-Scripting .
Evitar xargs se você pode:
man ruby | less -p 777
IFS=$'\777'
#array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null) )
array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null) )
echo ${#array[@]}
printf "%s\n" "${array[@]}" | nl
echo "${array[0]}"
IFS=$' \t\n'
Eu sou novo, mas eu acredito que esta uma resposta; espero que ajude alguém:
STYLE="$HOME/.fluxbox/styles/"
declare -a array1
LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f`
echo $LISTING
array1=( `echo $LISTING`)
TAR_SOURCE=`echo ${array1[@]}`
#tar czvf ~/FluxieStyles.tgz $TAR_SOURCE
Este é semelhante à versão de Stephan202, mas os arquivos (e diretórios) são colocados em uma matriz de uma só vez. O loop for
aqui é apenas para "fazer coisas úteis":
files=(*) # put files in current directory into an array
i=0
for file in "${files[@]}"
do
echo "File ${i}: ${file}" # do something useful
let i++
done
Para obter uma contagem:
echo ${#files[@]}
velha questão, mas ninguém sugeriu este método simples, então eu pensei que eu iria. Concedida se os seus nomes têm um ETX, isso não resolver o problema, mas eu suspeito que serve para qualquer cenário do mundo real. Tentando usar nulo parece entrar em conflito com IFS padrão manipulação regras. Tempere a seu gosto com as opções de localizar e tratamento de erros.
savedFS="$IFS"
IFS=$'\x3'
filenames=(`find wherever -printf %p$'\x3'`)
IFS="$savedFS"
A resposta de Gordon Davisson é grande para a festança. No entanto, uma exist atalho útil para usuários zsh:
Primeiro, coloque-lhe corda em uma variável:
A="$(find /tmp -type f -print0)"
Em seguida, dividir essa variável e armazená-lo em um array:
B=( ${(s/^@/)A} )
Existe um truque: ^@
é o caractere NUL. Para fazê-lo, você tem que digitar Ctrl + V seguido de Ctrl + @.
Você pode verificar cada entrada de US $ B contém valor correto:
for i in "$B[@]"; echo \"$i\"
leitores atento pode notar que chamada para comando find
pode ser evitado na maioria dos casos utilizando a sintaxe **
. Por exemplo:
B=( /tmp/** )
Bash nunca foi bom para lidar com nomes de arquivos (ou qualquer texto realmente) porque utiliza espaços como uma lista delimitador.
Eu recomendo usar python com a biblioteca sh .