Eu sempre vejo respostas citando este link dizendo definitivamente "Não analise ls
!" Isso me incomoda por alguns motivos:
Parece que as informações nesse link foram aceitas no atacado com poucas perguntas, embora eu possa detectar pelo menos alguns erros na leitura casual.
Também parece que os problemas mencionados nesse link não provocaram desejo de encontrar uma solução.
Do primeiro parágrafo:
... quando você solicita
[ls]
uma lista de arquivos, há um grande problema: o Unix permite quase qualquer caractere em um nome de arquivo, incluindo espaços em branco, novas linhas, vírgulas, símbolos de pipe e praticamente qualquer outra coisa que você já tentou usar como um arquivo. delimitador, exceto NUL. ...ls
separa nomes de arquivos com novas linhas. Tudo bem até você ter um arquivo com uma nova linha em seu nome. E como eu não conheço nenhuma implementaçãols
que permita que você encerre nomes de arquivos com caracteres NUL em vez de novas linhas, isso nos deixa incapazes de obter uma lista de nomes de arquivos com segurançals
.
Que chatice, certo? Como nunca podemos lidar com uma nova linha terminada conjunto de dados coletados para os dados que podem conter novas linhas? Bem, se as pessoas que respondem às perguntas neste site não faziam esse tipo de coisa diariamente, acho que estávamos com algum problema.
A verdade é que a maioria das ls
implementações realmente fornece uma API muito simples para analisar sua saída e todos nós fazemos isso o tempo todo, mesmo sem perceber. Não apenas você pode terminar um nome de arquivo com nulo, como também pode começar um com nulo ou com qualquer outra sequência arbitrária que desejar. Além disso, você pode atribuir essas seqüências arbitrárias por tipo de arquivo . Por favor considere:
LS_COLORS='lc=\0:rc=:ec=\0\0\0:fi=:di=:' ls -l --color=always | cat -A
total 4$
drwxr-xr-x 1 mikeserv mikeserv 0 Jul 10 01:05 ^@^@^@^@dir^@^@^@/$
-rw-r--r-- 1 mikeserv mikeserv 4 Jul 10 02:18 ^@file1^@^@^@$
-rw-r--r-- 1 mikeserv mikeserv 0 Jul 10 01:08 ^@file2^@^@^@$
-rw-r--r-- 1 mikeserv mikeserv 0 Jul 10 02:27 ^@new$
line$
file^@^@^@$
^@
Veja isso para mais.
Agora, é a próxima parte deste artigo que realmente me emociona:
$ ls -l
total 8
-rw-r----- 1 lhunath lhunath 19 Mar 27 10:47 a
-rw-r----- 1 lhunath lhunath 0 Mar 27 10:47 a?newline
-rw-r----- 1 lhunath lhunath 0 Mar 27 10:47 a space
O problema é que, a partir da saída de
ls
, nem você nem o computador podem dizer quais partes dele constituem um nome de arquivo. São cada palavra? Não. São cada linha? Não. Não há resposta correta para essa pergunta além de: você não pode dizer.Observe também como
ls
às vezes confunde os dados do seu nome de arquivo (no nosso caso, ele transformou o\n
caractere entre as palavras "a" e "nova linha" em um ? Ponto de interrogação ......
Se você deseja iterar sobre todos os arquivos no diretório atual, use um
for
loop e um glob:
for f in *; do
[[ -e $f ]] || continue
...
done
O autor chama de nomes de arquivos ilegíveis quando ls
retorna uma lista de nomes de arquivos que contêm globs de shell e recomenda o uso de um shell glob para recuperar uma lista de arquivos!
Considere o seguinte:
printf 'touch ./"%b"\n' "file\nname" "f i l e n a m e" |
. /dev/stdin
ls -1q
f i l e n a m e
file?name
IFS="
" ; printf "'%s'\n" $(ls -1q)
'f i l e n a m e'
'file
name'
O POSIX define os operandos -1
e -q
ls
assim:
-q
- Força cada instância de caracteres de nome de arquivo não imprimível<tab>
es a serem gravados como o caractere de ponto de interrogação ('?'
). As implementações podem fornecer essa opção por padrão, se a saída for para um dispositivo terminal.
-1
- (O dígito numérico um.) Força a saída a ser uma entrada por linha.
Globbing não tem seus próprios problemas - ?
corresponde a qualquer caractere, portanto vários ?
resultados correspondentes em uma lista correspondem ao mesmo arquivo várias vezes. Isso é facilmente manipulado.
Embora como fazer isso não seja o ponto - afinal, não é preciso muito para fazer e é demonstrado abaixo -, eu estava interessado em saber por que não . Na minha opinião, a melhor resposta para essa pergunta foi aceita. Eu sugiro que você tente se concentrar mais em dizer às pessoas o que elas podem fazer do que o que elas não podem. Você tem muito menos probabilidade, pelo que penso, de se provar errado, pelo menos.
Mas por que tentar? É certo que minha principal motivação era que outros continuavam me dizendo que eu não podia. Sei muito bem que o ls
resultado é tão regular e previsível quanto você deseja, desde que saiba o que procurar. A desinformação me incomoda mais do que a maioria das coisas.
Porém, a verdade é que, com a exceção notável das respostas de Patrick e Wumpus Q. Wumbley (apesar do incrível manuseio deste último) , considero a maioria das informações nas respostas aqui quase sempre corretas - um shell shell é mais simples de usar e geralmente mais eficaz quando se trata de pesquisar o diretório atual do que está analisando ls
. No entanto, elas não são, pelo menos no meu ponto de vista, razão suficiente para justificar a propagação das informações erradas citadas no artigo acima, nem são justificativas aceitáveis para " nunca analisarls
" .
Por favor, note que os resultados inconsistentes da resposta de Patrick são principalmente o resultado dele usando zsh
então bash
. zsh
- por padrão - o $(
comando de divisão de palavras não substitui os )
resultados de maneira portátil. Então, quando ele pergunta para onde foi o restante dos arquivos? a resposta para essa pergunta é que sua concha os comeu. É por isso que você precisa definir a SH_WORD_SPLIT
variável ao usar zsh
e lidar com o código do shell portátil. Considero sua falha em notar isso em sua resposta como terrivelmente enganosa.
A resposta de Wumpus não computa para mim - em um contexto de lista, o ?
personagem é uma bola de fogo. Não sei mais o que dizer.
Para lidar com um caso de vários resultados, você precisa restringir a ganância do globo. A seguir, basta criar uma base de teste com nomes de arquivos horríveis e exibi-la para você:
{ printf %b $(printf \\%04o `seq 0 127`) |
sed "/[^[-b]*/s///g
s/\(.\)\(.\)/touch '?\v\2' '\1\t\2' '\1\n\2'\n/g" |
. /dev/stdin
echo '`ls` ?QUOTED `-m` COMMA,SEP'
ls -qm
echo ; echo 'NOW LITERAL - COMMA,SEP'
ls -m | cat
( set -- * ; printf "\nFILE COUNT: %s\n" $# )
}
RESULTADO
`ls` ?QUOTED `-m` COMMA,SEP
??\, ??^, ??`, ??b, [?\, [?\, ]?^, ]?^, _?`, _?`, a?b, a?b
NOW LITERAL - COMMA,SEP
?
\, ?
^, ?
`, ?
b, [ \, [
\, ] ^, ]
^, _ `, _
`, a b, a
b
FILE COUNT: 12
Agora vou segura cada caractere que não é um /slash
, -dash
, :colon
, ou caractere alfa-numérico em uma glob de shell, em seguida, sort -u
a lista de resultados únicos. Isso é seguro porque ls
já protegemos quaisquer caracteres não imprimíveis para nós. Ver:
for f in $(
ls -1q |
sed 's|[^-:/[:alnum:]]|[!-\\:[:alnum:]]|g' |
sort -u | {
echo 'PRE-GLOB:' >&2
tee /dev/fd/2
printf '\nPOST-GLOB:\n' >&2
}
) ; do
printf "FILE #$((i=i+1)): '%s'\n" "$f"
done
RESULTADO:
PRE-GLOB:
[!-\:[:alnum:]][!-\:[:alnum:]][!-\:[:alnum:]]
[!-\:[:alnum:]][!-\:[:alnum:]]b
a[!-\:[:alnum:]]b
POST-GLOB:
FILE #1: '?
\'
FILE #2: '?
^'
FILE #3: '?
`'
FILE #4: '[ \'
FILE #5: '[
\'
FILE #6: '] ^'
FILE #7: ']
^'
FILE #8: '_ `'
FILE #9: '_
`'
FILE #10: '?
b'
FILE #11: 'a b'
FILE #12: 'a
b'
Abaixo, abordo o problema novamente, mas uso uma metodologia diferente. Lembre-se de que, além de \0
nulo, o /
caractere ASCII é o único byte proibido em um nome de caminho. Coloquei globs de lado aqui e, em vez disso, combine a -d
opção especificada POSIX para ls
e também a -exec $cmd {} +
construção especificada POSIX para find
. Como find
apenas um emitirá naturalmente /
em sequência, o item a seguir obtém facilmente uma lista de arquivos recursiva e delimitada de forma confiável, incluindo todas as informações de dentista para cada entrada. Imagine o que você pode fazer com algo assim:
#v#note: to do this fully portably substitute an actual newline \#v#
#v#for 'n' for the first sed invocation#v#
cd ..
find ././ -exec ls -1ldin {} + |
sed -e '\| *\./\./|{s||\n.///|;i///' -e \} |
sed 'N;s|\(\n\)///|///\1|;$s|$|///|;P;D'
###OUTPUT
152398 drwxr-xr-x 1 1000 1000 72 Jun 24 14:49
.///testls///
152399 -rw-r--r-- 1 1000 1000 0 Jun 24 14:49
.///testls/?
\///
152402 -rw-r--r-- 1 1000 1000 0 Jun 24 14:49
.///testls/?
^///
152405 -rw-r--r-- 1 1000 1000 0 Jun 24 14:49
.///testls/?
`///
...
ls -i
pode ser muito útil - especialmente quando a exclusividade do resultado está em questão.
ls -1iq |
sed '/ .*/s///;s/^/-inum /;$!s/$/ -o /' |
tr -d '\n' |
xargs find
Estes são apenas os meios mais portáteis em que consigo pensar. Com o GNU ls
você pode fazer:
ls --quoting-style=WORD
E por último, aqui está um método muito mais simples de analisarls
que, por acaso, uso com frequência quando preciso de números de inode:
ls -1iq | grep -o '^ *[0-9]*'
Isso apenas retorna números de inode - que é outra opção útil especificada pelo POSIX.
stat
minha resposta, pois ela realmente verifica se cada arquivo existe. Sua parte no fundo com a sed
coisa não funciona.
ls
em primeiro lugar? O que você está descrevendo é muito difícil. Vou precisar desconstruí-lo para entender tudo e sou um usuário relativamente competente. Você não pode esperar que seu Joe médio seja capaz de lidar com algo assim.
ls
saída está incorreta foram abordados bem no link original (e em muitos outros lugares). Essa pergunta teria sido razoável se o OP estivesse pedindo ajuda para entendê-lo, mas, em vez disso, o OP está simplesmente tentando provar que seu uso incorreto está correto.
parsing ls is bad
. Fazer for something in $(command)
e confiar na divisão de palavras para obter resultados precisos é ruim para a grande maioria dos command's
quais não possui saída simples.
time bash -c 'for i in {1..1000}; do ls -R &>/dev/null; done'
= 3.18s vstime bash -c 'for i in {1..1000}; do echo **/* >/dev/null; done'
= 1.28s