Alguém tem um script de shell de modelo para fazer algo com ls
uma lista de nomes de diretório e percorrer cada um deles e fazer alguma coisa?
Estou planejando fazer ls -1d */
para obter a lista de nomes de diretório.
Alguém tem um script de shell de modelo para fazer algo com ls
uma lista de nomes de diretório e percorrer cada um deles e fazer alguma coisa?
Estou planejando fazer ls -1d */
para obter a lista de nomes de diretório.
Respostas:
Editado para não usar ls onde uma glob faria, como @ shawn-j-goff e outros sugeriram.
Basta usar um for..do..done
loop:
for f in *; do
echo "File -> $f"
done
Você pode substituir o *
com *.txt
ou qualquer outro globo que retorne uma lista (de arquivos, diretórios ou qualquer outra coisa), um comando que gera uma lista, por exemplo $(cat filelist.txt)
, ou substituí-lo por uma lista.
Dentro do do
loop, você apenas se refere à variável do loop com o prefixo do cifrão ( $f
no exemplo acima). Você pode echo
fazer ou fazer qualquer outra coisa que desejar.
Por exemplo, para renomear todos os .xml
arquivos no diretório atual para .txt
:
for x in *.xml; do
t=$(echo $x | sed 's/\.xml$/.txt/');
mv $x $t && echo "moved $x -> $t"
done
Ou melhor ainda, se você estiver usando o Bash, poderá usar as expansões de parâmetro do Bash em vez de gerar um subshell:
for x in *.xml; do
t=${x%.xml}.txt
mv $x $t && echo "moved $x -> $t"
done
for
loops e uma armadilha típica de tentar analisar a saída de ls
. @ DanielA.White, você pode considerar não aceitar esta resposta se ela não foi útil (ou é potencialmente enganosa), pois como você disse, você está atuando em diretórios. A resposta de Shawn J. Goff deve fornecer uma solução mais robusta e funcional para o seu problema.
ls
saída , não deve ler a saída em for
loop , deve usar em $()
vez de `` e Usar mais cotações ™ . Tenho certeza que o @CoverosGene teve um bom resultado, mas isso é simplesmente terrível.
ls
é ls -1 | while read line; do stuff; done
. Pelo menos esse não será quebrado por espaços em branco.
mv -v
você não precisaecho "moved $x -> $t"
Usar a saída de ls
para obter nomes de arquivos é uma má idéia . Isso pode levar a scripts com mau funcionamento e até perigosos. Isso ocorre porque um nome de arquivo pode conter qualquer caractere, exceto /
e o null
caráter, e ls
não usa qualquer um desses caracteres como delimitadores, por isso, se um nome de arquivo tem um espaço ou uma nova linha, você vai obter resultados inesperados.
Existem duas maneiras muito boas de iterar sobre arquivos. Aqui, usei simplesmente echo
para demonstrar fazer algo com o nome do arquivo; você pode usar qualquer coisa.
A primeira é usar os recursos de globbing nativos do shell.
for dir in */; do
echo "$dir"
done
O shell se expande */
em argumentos separados que o for
loop lê; mesmo se houver espaço, nova linha ou qualquer outro caractere estranho no nome do arquivo, for
cada nome completo será visto como uma unidade atômica; não está analisando a lista de forma alguma.
Se você quiser entrar recursivamente em subdiretórios, isso não funcionará, a menos que o seu shell tenha alguns recursos de globbing estendidos (como bash
o s globstar
. Se o seu shell não tiver esses recursos, ou se você quiser garantir que seu script funcione em uma variedade de sistemas, a próxima opção é usar find
.
find . -type d -exec echo '{}' \;
Aqui, o find
comando chamará echo
e passará um argumento para o nome do arquivo. Faz isso uma vez para cada arquivo encontrado. Como no exemplo anterior, não há análise de uma lista de nomes de arquivos; em vez disso, um fileneame é enviado completamente como argumento.
A sintaxe do -exec
argumento parece um pouco engraçada. find
pega o primeiro argumento depois -exec
e trata isso como o programa a ser executado, e todo argumento subsequente, leva como argumento a ser transmitido para esse programa. Existem dois argumentos especiais que -exec
precisam ser vistos. O primeiro é {}
; esse argumento é substituído por um nome de arquivo find
gerado pelas partes anteriores . O segundo é ;
, que informa que find
este é o fim da lista de argumentos a serem passados para o programa; find
precisa disso, porque você pode continuar com mais argumentos destinados find
e não destinados ao programa exec'd. A razão para \
isso é que o shell também trata;
especialmente - ele representa o fim de um comando; portanto, precisamos escapar dele para que o shell o dê como argumento, em find
vez de consumi-lo por si mesmo; Outra maneira de fazer com que o shell não o trate especialmente é colocá-lo entre aspas: ';'
funciona tão bem quanto \;
para esse propósito.
find
. Há mágica que pode ser feita, e mesmo as limitações percebidas -exec
podem ser contornadas. A -print0
opção também é valiosa para uso com xargs
.
Para arquivos com espaços, certifique-se de citar a variável como:
for i in $(ls); do echo "$i"; done;
ou, você pode alterar a variável de ambiente separador de campo de entrada (IFS):
IFS=$'\n';for file in $(ls); do echo $i; done
Finalmente, dependendo do que você está fazendo, você pode nem precisar dos ls:
for i in *; do echo "$i"; done;
\n
é insuficiente. Nunca é uma boa idéia recomendar a análise da saída de ls
.
Se você possui o GNU Parallel http://www.gnu.org/software/parallel/ instalado, você pode fazer isso:
ls | parallel echo {} is in this dir
Para renomear todos os .txt para .xml:
ls *.txt | parallel mv {} {.}.xml
Assista ao vídeo de introdução do GNU Parallel para saber mais: http://www.youtube.com/watch?v=OpaiGYxkSuQ
Por que não definir o IFS para uma nova linha e capturar a saída de ls
em uma matriz? Definir o IFS como nova linha deve resolver problemas com caracteres engraçados nos nomes de arquivo; O uso ls
pode ser bom porque possui um recurso de classificação interno.
(Nos testes, eu estava tendo problemas para configurar o IFS, \n
mas configurá-lo para o backspace da nova linha funciona, conforme sugerido em outro lugar aqui):
Por exemplo (supondo que o ls
padrão de pesquisa desejado seja passado $1
):
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
FILES=($(/bin/ls "$1"))
for AFILE in ${FILES[@]}
do
... do something with a file ...
done
IFS=$SAVEIFS
Isso é especialmente útil no OS X, por exemplo, para capturar uma lista de arquivos classificados por data de criação (da mais antiga para a mais nova), o ls
comando é ls -t -U -r
.
\n
é insuficiente. As únicas soluções válidas usam um loop for com expansão do nome do caminho ou find
.
É assim que eu faço, mas provavelmente existem maneiras mais eficientes.
ls > filelist.txt
while read filename; do echo filename: "$filename"; done < filelist.txt
ls | while read i; do echo filename: $i; done