Vedo costantemente le risposte che citano questo link affermando definitivamente "Non analizzare ls
!" Questo mi dà fastidio per un paio di motivi:
Sembra che le informazioni contenute in quel link siano state accettate all'ingrosso con poca domanda, anche se posso rilevare almeno alcuni errori nella lettura casuale.
Sembra anche che i problemi indicati in quel link non abbiano suscitato il desiderio di trovare una soluzione.
Dal primo paragrafo:
... quando chiedi
[ls]
un elenco di file, c'è un grosso problema: Unix consente quasi qualsiasi carattere in un nome file, inclusi spazi bianchi, nuove righe, virgole, simboli di pipe e praticamente qualsiasi altra cosa che avresti mai provato a usare come delimitatore tranne NUL. ...ls
separa i nomi dei file con nuove righe. Questo va bene fino a quando non hai un file con una nuova riga nel suo nome. E poiché non conosco alcuna implementazionels
che consenta di terminare i nomi di file con caratteri NUL invece di newline, ciò non ci consente di ottenere un elenco di nomi di file in modo sicurols
.
Peccato, vero? Come mai possiamo gestire un set di dati elencato terminato da una nuova riga per i dati che potrebbero contenere nuove righe? Bene, se le persone che rispondono alle domande su questo sito Web non facessero questo genere di cose su base giornaliera, potrei pensare che ci fossero dei problemi.
La verità è che la maggior parte delle ls
implementazioni in realtà fornisce un'API molto semplice per analizzare il loro output e lo abbiamo sempre fatto senza nemmeno accorgercene. Non solo puoi terminare un nome file con null, ma puoi anche iniziarne uno con null o con qualsiasi altra stringa arbitraria che potresti desiderare. Inoltre, puoi assegnare queste stringhe arbitrarie per tipo di file . Si prega di prendere in considerazione:
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^@^@^@$
^@
Vedi questo per di più.
Ora è la parte successiva di questo articolo che mi prende davvero:
$ 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
Il problema è che dall'output di
ls
, né tu né il computer potete dire quali parti di esso costituiscono un nome file. È ogni parola? No. È ogni riga? No. Non esiste una risposta corretta a questa domanda oltre a: non si può dire.Nota anche che a
ls
volte altera i dati del tuo nome file (nel nostro caso, ha trasformato il\n
carattere tra le parole "a" e "newline" in un punto interrogativo ......
Se vuoi solo iterare su tutti i file nella directory corrente, usa un
for
ciclo e un glob:
for f in *; do
[[ -e $f ]] || continue
...
done
L'autore lo chiama confondendo i nomi dei file quando ls
restituisce un elenco di nomi di file contenenti globs di shell e quindi consiglia di utilizzare un glob di shell per recuperare un elenco di file!
Considera quanto segue:
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'
POSIX definisce gli operandi -1
e -q
ls
così:
-q
- Forza ogni istanza di caratteri e di nomi di file non stampabili<tab>
da scrivere come carattere punto interrogativo ('?'
). Le implementazioni possono fornire questa opzione per impostazione predefinita se l'output è verso un dispositivo terminale.
-1
- (Una cifra numerica.) Forza l'uscita a una voce per riga.
Il globbing non è privo di problemi: ?
corrisponde a qualsiasi carattere, quindi più ?
risultati di corrispondenza in un elenco corrisponderanno più volte allo stesso file. È facilmente gestibile.
Anche se come fare questa cosa non è il punto - non ci vuole molto da fare dopo tutto ed è dimostrato di seguito - ero interessato a perché no . A mio avviso, la migliore risposta a questa domanda è stata accettata. Ti suggerirei di provare a concentrarti più spesso sul dire alle persone cosa possono fare piuttosto che su ciò che non possono. Almeno è molto meno probabile, a mio avviso, essere smentito.
Ma perché provarci? Certo, la mia motivazione principale era che gli altri continuavano a dirmi che non potevo. So benissimo che l' ls
output è regolare e prevedibile come si potrebbe desiderare fintanto che si sa cosa cercare. La disinformazione mi dà più fastidio di quanto non faccia la maggior parte delle cose.
La verità è, tuttavia, con la notevole eccezione delle risposte sia di Patrick che di Wumpus Q. Wumbley (nonostante la straordinaria impugnatura di quest'ultimo) , considero la maggior parte delle informazioni nelle risposte qui come per lo più corrette - un guscio glob è entrambi più semplice da usare e generalmente più efficace quando si tratta di cercare nella directory corrente di quanto non sia l'analisi ls
. Non sono, tuttavia, almeno a mio avviso, una ragione sufficiente per giustificare la propagazione della disinformazione citata nell'articolo precedente, né sono giustificazioni accettabili per " non analizzare mails
" .
Si noti che i risultati incoerenti della risposta di Patrick sono principalmente il risultato del suo utilizzo di zsh
allora bash
. zsh
- per impostazione predefinita - il $(
comando di divisione delle parole non sostituisce i )
risultati in modo portatile. Quindi quando chiede dove sono finiti gli altri file? la risposta a questa domanda è che la tua conchiglia li ha mangiati. Questo è il motivo per cui è necessario impostare la SH_WORD_SPLIT
variabile quando si utilizza zsh
e si gestisce il codice della shell portatile. Considero la sua incapacità di annotarlo nella sua risposta come terribilmente fuorviante.
La risposta di Wumpus non viene calcolata per me - in un contesto di lista il ?
personaggio è un glob shell. Non so come altro dirlo.
Per gestire un caso con più risultati è necessario limitare l'avidità del mondo. Quanto segue creerà solo una base di test di nomi di file orribili e lo visualizzerà per te:
{ 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" $# )
}
PRODUZIONE
`ls` ?QUOTED `-m` COMMA,SEP
??\, ??^, ??`, ??b, [?\, [?\, ]?^, ]?^, _?`, _?`, a?b, a?b
NOW LITERAL - COMMA,SEP
?
\, ?
^, ?
`, ?
b, [ \, [
\, ] ^, ]
^, _ `, _
`, a b, a
b
FILE COUNT: 12
Ora sarò al sicuro ogni personaggio che non è una /slash
, -dash
, :colon
, o un carattere alfanumerico in un glob di shell, allora sort -u
la lista per risultati unici. Questo è sicuro perché ls
ha già salvato tutti i caratteri non stampabili per noi. Orologio:
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
PRODUZIONE:
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'
Di seguito mi rivolgo nuovamente al problema ma utilizzo una metodologia diversa. Ricorda che - oltre a \0
null - il /
carattere ASCII è l'unico byte proibito in un nome percorso. Metto da parte i globs e invece combino l' -d
opzione specificata POSIX per ls
e anche il -exec $cmd {} +
costrutto specificato POSIX per find
. Poiché ne find
uscirà sempre e solo naturalmente uno /
in sequenza, ciò che segue procura facilmente un elenco di file ricorsivo e delimitato in modo affidabile che include tutte le informazioni sull'odontoiatria per ogni voce. Immagina cosa potresti fare con qualcosa del genere:
#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
può essere molto utile, soprattutto quando si tratta dell'unicità del risultato.
ls -1iq |
sed '/ .*/s///;s/^/-inum /;$!s/$/ -o /' |
tr -d '\n' |
xargs find
Questi sono solo i mezzi più portatili a cui riesco a pensare. Con GNU ls
puoi fare:
ls --quoting-style=WORD
E infine, ecco un metodo molto più semplice di analisils
che mi capita di usare abbastanza spesso quando ho bisogno di numeri di inode:
ls -1iq | grep -o '^ *[0-9]*'
Ciò restituisce solo i numeri di inode, che è un'altra utile opzione POSIX specificata.
stat
mia risposta, in quanto verifica effettivamente che ogni file esista. Il tuo bit in fondo alla sed
cosa non funziona.
ls
in primo luogo? Quello che stai descrivendo è molto difficile. Dovrò decostruirlo per capirlo e sono un utente relativamente competente. Non puoi certo aspettarti che il tuo Joe medio sia in grado di affrontare qualcosa del genere.
ls
dell'output è errato sono stati trattati bene nel link originale (e in molti altri luoghi). Questa domanda sarebbe stata ragionevole se OP stesse chiedendo aiuto per capirla, ma invece OP sta semplicemente cercando di dimostrare che il suo uso errato è ok.
parsing ls is bad
. Fare for something in $(command)
e fare affidamento sulla suddivisione delle parole per ottenere risultati accurati è un male per la maggior parte dei command's
quali non ha un output semplice.
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