Perché * non * analizzare `ls` (e cosa fare invece)?


204

Vedo costantemente le risposte che citano questo link affermando definitivamente "Non analizzare ls!" Questo mi dà fastidio per un paio di motivi:

  1. 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.

  2. 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. ... lssepara 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 implementazione lsche 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 sicuro ls.

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 lsimplementazioni 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 lsvolte altera i dati del tuo nome file (nel nostro caso, ha trasformato il \ncarattere tra le parole "a" e "newline" in un punto interrogativo ...

...

Se vuoi solo iterare su tutti i file nella directory corrente, usa un forciclo e un glob:

for f in *; do
    [[ -e $f ]] || continue
    ...
done

L'autore lo chiama confondendo i nomi dei file quando lsrestituisce 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 -1e -q lscosì:

-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' lsoutput è 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 zshallora 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_SPLITvariabile quando si utilizza zshe 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 -ula lista per risultati unici. Questo è sicuro perché lsha 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 \0null - il /carattere ASCII è l'unico byte proibito in un nome percorso. Metto da parte i globs e invece combino l' -dopzione specificata POSIX per lse anche il -exec $cmd {} +costrutto specificato POSIX per find. Poiché ne finduscirà 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 lspuoi 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.


12
@mikeserv Ok l'ho fatto. Shell glob è 2,48 volte più veloce. time bash -c 'for i in {1..1000}; do ls -R &>/dev/null; done'= 3.18s vs time bash -c 'for i in {1..1000}; do echo **/* >/dev/null; done'= 1.28s
Patrick

28
Per quanto riguarda il tuo aggiornamento più recente, smetti di fare affidamento sull'output visivo per determinare se il tuo codice funziona. Passa l'output a un programma reale e chiedi al programma di provare a eseguire un'operazione sul file. Questo è il motivo per cui stavo usando la statmia risposta, in quanto verifica effettivamente che ogni file esista. Il tuo bit in fondo alla sedcosa non funziona.
Patrick,

57
Non puoi essere serio. In che modo saltare attraverso tutti i cerchi descritti dalla tua domanda può essere più facile o più semplice o in qualche modo migliore del semplice non analizzare lsin 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.
terdon

46
-1 per l'utilizzo di una domanda per selezionare un argomento. Tutti i motivi per cui l'analisi lsdell'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.
R ..

14
@mikeserv Non è solo quello 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'squali non ha un output semplice.
BroSlow,

Risposte:


184

Io non sono affatto convinti di questo, ma supponiamo per amor di discussione che si potrebbe , se siete disposti a mettere in uno sforzo sufficiente, analizzare l'output di lsaffidabile, anche a fronte di un "avversario" - qualcuno che conosce il codice che hai scritto e sceglie deliberatamente nomi di file progettati per romperlo.

Anche se tu potessi farlo, sarebbe comunque una cattiva idea .

La shell Bourne non è una buona lingua. Non dovrebbe essere usato per nulla di complicato, a meno che la portabilità estrema sia più importante di qualsiasi altro fattore (ad es autoconf.).

Sostengo che se stai affrontando un problema in cui l'analisi dell'output di lssembra il percorso di minor resistenza per uno script di shell, ciò indica chiaramente che qualsiasi cosa tu stia facendo è troppo complicata per la shell e dovresti riscrivere l'intera cosa in Perl o Python. Ecco il tuo ultimo programma in Python:

import os, sys
for subdir, dirs, files in os.walk("."):
    for f in dirs + files:
      ino = os.lstat(os.path.join(subdir, f)).st_ino
      sys.stdout.write("%d %s %s\n" % (ino, subdir, f))

Questo non ha alcun problema con caratteri insoliti nei nomi dei file - l' output è ambiguo allo stesso modo in cui l'output di lsè ambiguo, ma questo non avrebbe importanza in un programma "reale" (al contrario di una demo come questa), che usa il risultato di os.path.join(subdir, f)direttamente.

Altrettanto importante, e in netto contrasto con ciò che hai scritto, avrà ancora senso tra sei mesi e sarà facile modificarlo quando ne avrai bisogno per fare qualcosa di leggermente diverso. A titolo illustrativo, supponiamo di scoprire la necessità di escludere dotfile e backup dell'editor e di elaborare tutto in ordine alfabetico per nome base:

import os, sys
filelist = []
for subdir, dirs, files in os.walk("."):
    for f in dirs + files:
        if f[0] == '.' or f[-1] == '~': continue
        lstat = os.lstat(os.path.join(subdir, f))
        filelist.append((f, subdir, lstat.st_ino))

filelist.sort(key = lambda x: x[0])
for f, subdir, ino in filelist: 
   sys.stdout.write("%d %s %s\n" % (ino, subdir, f))

5
Questo è buono. Ritiene che for in | for inparlano di ricorsione? Non ne sono sicuro. Anche se non può essere più di uno, giusto? Questa è l'unica risposta che ha senso per me finora.
Mikeserv,

10
Nessuna ricorsione, solo forcicli nidificati . os.walksta facendo un po 'di sollevamento serio dietro le quinte, ma non devi preoccuparti più di quanto devi preoccuparti di come lso di findlavorare internamente.
zwol,

6
Tecnicamente, os.walkrestituisce un oggetto generatore . I generatori sono la versione di Lazy List di Python. Ogni volta che il ciclo for esterno scorre, il generatore viene invocato e "produce" il contenuto di un'altra sottodirectory. Funzionalità equivalente in Perl è File::Find, se ciò aiuta.
zwol,

6
Dovresti essere consapevole che sono d'accordo al 100% con il documento che stai criticando e con le risposte di Patrick e Terdon. La mia risposta intendeva fornire un motivo aggiuntivo e indipendente per evitare l'analisi lsdell'output.
zwol,

19
Questo è molto fuorviante. Shell non è un buon linguaggio di programmazione, ma solo perché non è un linguaggio di programmazione. È un linguaggio di scripting. Ed è un buon linguaggio di scripting.
Miles Rout,

178

Tale link è referenziato molto perché le informazioni sono completamente accurate ed è stato lì per molto tempo.


lssostituisce i caratteri non stampabili con caratteri glob sì, ma quei caratteri non si trovano nel nome file effettivo. Perché è importante? 2 motivi:

  1. Se si passa quel nome file a un programma, quel nome file non esiste realmente. Dovrebbe espandere il glob per ottenere il vero nome del file.
  2. Il file glob potrebbe corrispondere a più di un file.

Per esempio:

$ touch a$'\t'b
$ touch a$'\n'b
$ ls -1
a?b
a?b

Nota come abbiamo 2 file che sembrano esattamente uguali. Come li distinguerai se entrambi sono rappresentati come a?b?


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!

C'è una differenza qui. Quando si recupera un glob, come mostrato, quel glob potrebbe corrispondere a più di un file. Tuttavia, quando si scorre i risultati corrispondenti a un glob, si ottiene il file esatto, non un glob.

Per esempio:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

Notare come l' xxdoutput mostra che $fileconteneva i caratteri non elaborati \te \nnon ?.

Se lo usi ls, ottieni invece questo:

for file in $(ls -1q); do printf '%s' "$file" | xxd; done
0000000: 613f 62                                  a?b
0000000: 613f 62                                  a?b

"Ho intenzione di iterare comunque, perché non usarlo ls?"

Il tuo esempio che hai dato in realtà non funziona. Sembra che funzioni, ma non lo fa.

Mi riferisco a questo:

 for f in $(ls -1q | tr " " "?") ; do [ -f "$f" ] && echo "./$f" ; done

Ho creato una directory con un sacco di nomi di file:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6120 62                                  a b
0000000: 6120 2062                                a  b
0000000: 61e2 8082 62                             a...b
0000000: 61e2 8083 62                             a...b
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

Quando eseguo il tuo codice, ottengo questo:

$ for f in $(ls -1q | tr " " "?") ; do [ -f "$f" ] && echo "./$f" ; done
./ab
./ab

Dov'è finito il resto dei file?

Proviamo invece questo:

$ for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f"; done
stat: cannot stat ‘./a?b’: No such file or directory
stat: cannot stat ‘./a??b’: No such file or directory
./ab
./ab
stat: cannot stat ‘./a?b’: No such file or directory
stat: cannot stat ‘./a?b’: No such file or directory

Ora usiamo un glob reale:

$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a  b
./ab
./ab
./a b
./a
b

Con bash

L'esempio sopra è stato con la mia shell normale, zsh. Quando ripeto la procedura con bash, ottengo un altro set di risultati completamente diverso con il tuo esempio:

Stesso set di file:

$ for file in *; do printf '%s' "$file" | xxd; done
0000000: 6120 62                                  a b
0000000: 6120 2062                                a  b
0000000: 61e2 8082 62                             a...b
0000000: 61e2 8083 62                             a...b
0000000: 6109 62                                  a.b
0000000: 610a 62                                  a.b

Risultati radicalmente diversi con il tuo codice:

for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f"; done
./a b
./ab
./ab
./a b
./a
b
./a  b
./ab
./ab
./a b
./ab
./ab
./a b
./a
b
./a b
./ab
./ab
./a b
./a
b

Con un guscio glob, funziona perfettamente:

$ for f in *; do stat --format='%n' "./$f"; done
./a b
./a  b
./ab
./ab
./a b
./a
b

Il motivo per cui bash si comporta in questo modo risale a uno dei punti che ho sollevato all'inizio della risposta: "Il file glob potrebbe corrispondere a più di un file".

lsrestituisce lo stesso glob ( a?b) per diversi file, quindi ogni volta che espandiamo questo glob, otteniamo ogni singolo file corrispondente.


Come ricreare l'elenco di file che stavo usando:

touch 'a b' 'a  b' a$'\xe2\x80\x82'b a$'\xe2\x80\x83'b a$'\t'b a$'\n'b

I codici esadecimali sono caratteri NBF UTF-8.


5
@mikeserv in realtà la sua soluzione non restituisce un glob. Ho appena aggiornato la mia risposta per chiarire questo punto.
Patrick,

18
"Non il resto"? È un comportamento incoerente e risultati inaspettati, come mai non è una ragione?
Patrick,

11
@mikeserv Non hai visto il mio commento sulla tua domanda? Shell globbing è 2,5 volte più veloce di ls. Ho anche richiesto di testare il codice in quanto non funziona. Cosa c'entra zsh con tutto questo?
Patrick,

27
@mikeserv No, tutto si applica anche a bash. Anche se ho finito con questa domanda perché non stai ascoltando quello che sto dicendo.
Patrick,

7
Sai cosa, penso che voterò questa risposta e chiarirò nel mio che sono d'accordo con tutto ciò che dice. ;-)
zwol,

54

Proviamo a semplificare un po ':

$ touch a$'\n'b a$'\t'b 'a b'
$ ls
a b  a?b  a?b
$ IFS="
"
$ set -- $(ls -1q | uniq)
$ echo "Total files in shell array: $#"
Total files in shell array: 4

Vedere? È già sbagliato proprio lì. Ci sono 3 file ma bash riporta 4. Ciò è dovuto al fatto che setvengono dati i globs generati dai lsquali vengono espansi dalla shell prima di essere passati set. Ecco perché ottieni:

$ for x ; do
>     printf 'File #%d: %s\n' $((i=$i+1)) "$x"
> done
File #1: a b
File #2: a b
File #3: a    b
File #4: a
b

Oppure, se preferisci:

$ printf ./%s\\0 "$@" |
> od -A n -c -w1 |
> sed -n '/ \{1,3\}/s///;H
> /\\0/{g;s///;s/\n//gp;s/.*//;h}'
./a b
./a b
./a\tb
./a\nb

Quanto sopra è stato eseguito bash 4.2.45.


2
Ho votato questo. È bello vedere che il tuo codice ti morde. Ma solo perché ho sbagliato, non significa che non possa essere fatto bene. Ti ho mostrato un modo molto semplice per farlo stamattina ls -1qRi | grep -o '^ *[0-9]*': analizzare l' lsoutput, amico, ed è il modo più veloce e migliore che io conosca per ottenere un elenco di numeri di inode.
mikeserv,

38
@mikeserv: potrebbe essere fatto bene, se hai tempo e pazienza. Ma il fatto è che è intrinsecamente soggetto a errori. Tu stesso hai sbagliato. mentre discute sui suoi meriti! Questo è un enorme sciopero contro di essa, se anche l'unica persona che lotta per questo non riesce a farlo correttamente. E è probabile che probabilmente passerai ancora più tempo a sbagliarti prima di farlo bene. Non so te, ma la maggior parte delle persone ha a che fare con il loro tempo piuttosto che giocherellare per anni con la stessa linea di codice.
cHao,

@cHao - non ho discusso i suoi meriti - ho protestato contro la sua propaganda.
Mikeserv,

16
@mikeserv: gli argomenti contro di essa sono fondati e meritati. Anche tu hai dimostrato che sono vere.
cHao,

1
@cHao - non sono d'accordo. C'è una linea non così sottile tra un mantra e una saggezza.
Mikeserv,

50

L'output di ls -qnon è affatto un glob. Usa ?per significare "C'è un personaggio qui che non può essere visualizzato direttamente". I globi usano ?per dire "Qui è permesso qualsiasi personaggio".

I globi hanno altri caratteri speciali ( *e []almeno, e all'interno della []coppia ce ne sono altri). Nessuno di questi è sfuggito a ls -q.

$ touch x '[x]'
$ ls -1q
[x]
x

Se tratti l' ls -1qoutput ci sono una serie di globs e li espandi, non solo otterrai xdue volte, ma ti perderai [x]completamente. Come glob, non corrisponde a se stesso come una stringa.

ls -q è pensato per salvare gli occhi e / o il terminale da personaggi folli, non per produrre qualcosa che puoi alimentare con il guscio.


42

La risposta è semplice: i casi speciali che lsdevi affrontare superano ogni possibile beneficio. Questi casi speciali possono essere evitati se non si analizza l' lsoutput.

Il mantra qui non è mai fidarsi del filesystem dell'utente (l'equivalente di non fidarsi mai dell'input dell'utente ). Se esiste un metodo che funzionerà sempre, con certezza al 100%, dovrebbe essere il metodo che preferisci anche se lsfa lo stesso ma con meno certezza. Non entrerò nei dettagli tecnici poiché quelli sono stati ampiamente trattati da Terdon e Patrick . So che a causa dei rischi derivanti dall'utilizzo lsdi un'importante (e forse costosa) transazione in cui il mio lavoro / prestigio è in linea, preferirò qualsiasi soluzione che non abbia un grado di incertezza se può essere evitata.

So che alcune persone preferiscono un certo rischio rispetto alla certezza , ma ho presentato una segnalazione di bug .


33

Il motivo per cui la gente dice che non fare mai qualcosa non è necessariamente perché non può assolutamente essere fatto in modo assolutamente positivo. Potremmo essere in grado di farlo, ma potrebbe essere più complicato, meno efficiente sia dal punto di vista spaziale che temporale. Ad esempio, sarebbe perfettamente corretto dire "Non creare mai un back-end di e-commerce di grandi dimensioni nell'assembly x86".

Quindi ora al problema in questione: come hai dimostrato, puoi creare una soluzione che analizza ls e dà il risultato giusto, quindi la correttezza non è un problema.

È più complicato? Sì, ma possiamo nasconderlo dietro una funzione di supporto.

Quindi ora per l'efficienza:

Efficienza nello spazio: la tua soluzione si basa sul uniqfiltraggio dei duplicati, di conseguenza non possiamo generare pigramente i risultati. Quindi o O(1)contro O(n)o entrambi hanno O(n).

Efficienza temporale: il caso migliore uniqutilizza un approccio hashmap, quindi abbiamo ancora un O(n)algoritmo nel numero di elementi acquistati , probabilmente anche se lo è O(n log n).

Ora il vero problema: mentre il tuo algoritmo non sta ancora andando male, sono stato davvero attento a usare elementi procurati e non elementi per n. Perché questo fa una grande differenza. Supponi di avere un file \n\nche si tradurrà in un glob per ??cui abbina ogni 2 file di caratteri nell'elenco. Stranamente se hai un altro file \n\rche risulterà ??e restituirà anche tutti i 2 file di caratteri .. vedi dove sta andando? Il comportamento esponenziale anziché lineare si qualifica certamente come "comportamento peggiore in fase di esecuzione" .. è la differenza tra un algoritmo pratico e uno su cui si scrivono articoli in riviste teoriche CS.

Tutti adorano gli esempi, giusto? Eccoci qui. Crea una cartella chiamata "test" e usa questo script python nella stessa directory in cui si trova la cartella.

#!/usr/bin/env python3
import itertools
dir = "test/"
filename_length = 3
options = "\a\b\t\n\v\f\r"

for filename in itertools.product(options, repeat=filename_length):
        open(dir + ''.join(filename), "a").close()

L'unica cosa che fa è generare tutti i prodotti di lunghezza 3 per 7 caratteri. La matematica del liceo ci dice che dovrebbero essere 343 file. Bene, dovrebbe essere davvero veloce da stampare, quindi vediamo:

time for f in *; do stat --format='%n' "./$f" >/dev/null; done
real    0m0.508s
user    0m0.051s
sys 0m0.480s

Ora proviamo la tua prima soluzione, perché non riesco davvero a capirlo

eval set -- $(ls -1qrR ././ | tr ' ' '?' |
sed -e '\|^\(\.\{,1\}\)/\.\(/.*\):|{' -e \
        's//\1\2/;\|/$|!s|.*|&/|;h;s/.*//;b}' -e \
        '/..*/!d;G;s/\(.*\)\n\(.*\)/\2\1/' -e \
        "s/'/'\\\''/g;s/.*/'&'/;s/?/'[\"?\$IFS\"]'/g" |
uniq)

cosa qui per lavorare su Linux mint 16 (che penso parla di volumi per l'usabilità di questo metodo).

Comunque dal momento che quanto sopra filtra praticamente solo il risultato dopo averlo ottenuto, la soluzione precedente dovrebbe essere veloce almeno quanto la successiva (nessun trucco di inode in quello - ma quelli sono inaffidabili quindi rinunciare alla correttezza).

Quindi ora quanto dura

time for f in $(ls -1q | tr " " "?") ; do stat --format='%n' "./$f" >/dev/null; done

prendere? Beh, davvero non lo so, ci vuole un po 'per controllare i nomi dei file 343 ^ 343 - te lo dirò dopo la morte calda dell'universo.


6
Naturalmente, come menzionato nei commenti sotto un'altra risposta , l'affermazione "... hai dimostrato di poter creare una soluzione che analizza ls e dà il giusto risultato ..." non è in realtà vera.
Wildcard il

26

Intenzione dichiarata di OP indirizzata

prefazione e motivazione della risposta originale aggiornata il 18/05/2015

mikeserv (il PO) ha dichiarato nell'ultimo aggiornamento della sua domanda: "Ritengo vergognoso che abbia posto prima questa domanda per indicare una fonte di disinformazione e, sfortunatamente, la risposta più votata qui è in gran parte fuorviante. "

Allora ok; Mi sento era piuttosto un peccato che ho trascorso così tanto tempo a cercare di capire come spiegare il mio senso solo per scoprire che , come ho ri-leggere la domanda. Questa domanda finì per "[generare] discussioni piuttosto che risposte" e finì con un peso di ~ 18K di testo (solo per la domanda, solo per essere chiari) che sarebbe lungo anche per un post sul blog.

StackExchange non è il tuo soapbox e non è il tuo blog. Tuttavia, in effetti, l'hai usato come almeno un po 'di entrambi. Le persone hanno finito per passare molto tempo a rispondere al "To-Point-Out" invece di rispondere alle domande reali delle persone. A questo punto segnerò la domanda come non adatta al nostro formato, dato che l'OP ha dichiarato esplicitamente che non era nemmeno destinata a essere una domanda.

A questo punto non sono sicuro se la mia risposta fosse o meno al punto; probabilmente no, ma era diretto ad alcune delle tue domande e forse può essere una risposta utile a qualcun altro; i principianti prendono il cuore, alcuni di quelli che "non" si trasformano in "qualche volta" una volta acquisiti maggiore esperienza. :)

Come regola generale...

per favore perdona i bordi grezzi rimanenti; Ho già trascorso troppo tempo su questo ... piuttosto che citare direttamente l'OP (come inizialmente previsto) cercherò di riassumere e parafrasare.

[ampiamente rielaborato dalla mia risposta originale] a
seguito di riflessione, credo di aver letto male l'enfasi che il PO stava ponendo sulle domande a cui ho risposto; tuttavia, i punti affrontati sono stati sollevati e ho lasciato le risposte in gran parte intatte in quanto ritengo che siano al punto e per affrontare i problemi che ho visto sollevati in altri contesti e per quanto riguarda la consulenza ai principianti.

Il post originale chiedeva, in vari modi, perché vari articoli fornivano consigli come «Non analizzare l' lsoutput» o «Non dovresti mai analizzare l' lsoutput» e così via.

La mia soluzione suggerita al problema è che i casi di questo tipo di affermazione sono semplicemente esempi di un linguaggio, espresso in modi leggermente diversi, in cui un quantificatore assoluto è accoppiato con un imperativo [ad esempio, "non [mai] X", «[Dovresti] sempre Y», «[non dovresti] mai Z»] per formare dichiarazioni intese ad essere usate come regole generali o linee guida, specialmente se date a coloro che non conoscono un soggetto, anziché essere intese come verità assolute, nonostante la forma apparente di tali dichiarazioni.

Quando inizi a imparare nuovi argomenti e, a meno che tu non abbia una buona comprensione del motivo per cui potresti dover fare diversamente, è una buona idea semplicemente seguire le regole generali accettate senza eccezioni, a meno che non sia guidato da qualcuno più esperto tu stesso. Con abilità ed esperienza crescenti diventerai ulteriormente in grado di determinare quando e se una regola si applica in una situazione particolare. Una volta che avrai raggiunto un livello significativo di esperienza, probabilmente capirai in primo luogo il ragionamento alla base della regola generale, e a quel punto puoi iniziare a usare il tuo giudizio sul se e su quale livello si applicano le ragioni alla base della regola quella situazione, e anche se ci sono forse preoccupazioni prevalenti.

Ed è allora che un esperto, forse, potrebbe scegliere di fare le cose in violazione di "Le regole". Ma ciò non li renderebbe meno "Le regole".

E, quindi, per l'argomento in questione: a mio avviso, solo perché un esperto potrebbe essere in grado di violare questa regola senza essere completamente violato, non vedo in alcun modo che potresti giustificare dicendo a un principiante che "a volte" è va bene analizzare lsl' analisi , perché: non lo è . O, almeno, certamente non è giusto che un principiante lo faccia.

Metti sempre le pedine al centro; nell'apertura un pezzo, una mossa; castello alla prima occasione; cavalieri davanti ai vescovi; un cavaliere sul bordo è triste; e assicurati sempre di poter vedere il tuo calcolo fino alla fine! (Spiacenti, scusa, stanchezza, è per gli scacchi StackExchange.)

Regole, destinato ad essere infranto?

Quando leggi un articolo su un argomento che è indirizzato o che è probabile che venga letto dai principianti, vedrai spesso cose come queste:

  • "Non dovresti mai fare X."
  • "Non fare mai Q!"
  • "Non fare Z."
  • "Uno dovrebbe sempre fare Y!"
  • "C, non importa cosa."

Mentre queste affermazioni sembrano certamente affermare regole assolute e senza tempo, non lo sono; invece questo è un modo per affermare regole generali [aka "linee guida", "regole empiriche", "le basi", ecc.) che è almeno probabilmente un modo appropriato per dichiararle per i principianti che potrebbero leggere quegli articoli. Tuttavia, proprio perché sono dichiarati come assoluti, le regole certamente non vincolano professionisti ed esperti [che erano probabilmente quelli che hanno riassunto tali regole in primo luogo, come un modo per registrare e trasmettere le conoscenze acquisite mentre si occupavano delle ricorrenze problemi nel loro particolare mestiere.]

Quelle regole certamente non riveleranno come un esperto affronterebbe un problema complesso o sfumato, in cui, diciamo, quelle regole sono in conflitto tra loro; o in cui le preoccupazioni che hanno portato alla regola in primo luogo semplicemente non si applicano. Gli esperti non hanno paura di (o non dovrebbero aver paura di!) Semplicemente infrangere le regole che capiscono di non avere senso in una situazione particolare. Gli esperti hanno a che fare costantemente con il bilanciamento di vari rischi e preoccupazioni nel loro mestiere e devono spesso usare il loro giudizio per scegliere di infrangere quel tipo di regole, dover bilanciare vari fattori e non poter fare affidamento solo su una tabella di regole da seguire. Prendi Gotocome esempio: c'è stato un lungo, ricorrente, dibattito sul fatto che siano dannosi. (Sì, non usare mai goto.; D)

Una proposta modale

Una caratteristica strana, almeno in inglese, e immagino in molte altre lingue, di regole generali, è che sono dichiarate nella stessa forma di una proposta modale, eppure gli esperti in un campo sono disposti a dare una regola generale per un situazione, sapendo nel frattempo che infrangeranno la regola quando appropriato. Chiaramente, quindi, queste affermazioni non sono pensate per essere equivalenti alle stesse affermazioni nella logica modale.

Questo è il motivo per cui dico che devono essere semplicemente idiomatici. Piuttosto che essere veramente una situazione "mai" o "sempre", queste regole di solito servono a codificare linee guida generali che tendono ad essere appropriate in una vasta gamma di situazioni e che, quando i principianti le seguono alla cieca, rischiano di provocare molto risultati migliori rispetto al principiante che sceglie di andare contro di loro senza una buona ragione. A volte codificano le regole semplicemente portando a risultati scadenti piuttosto che i veri e propri fallimenti che accompagnano le scelte errate quando vanno contro le regole.

Quindi, le regole generali non sono le proposizioni modali assolute che sembrano essere in superficie, ma invece sono un modo abbreviato di dare la regola con una piastra standard implicita, qualcosa di simile al seguente:

a meno che tu non abbia la capacità di dire che questa linea guida non è corretta in un caso particolare e di dimostrare a te stesso che hai ragione, allora $ {RULE}

dove, ovviamente, è possibile sostituire "mai analizzare l' lsoutput" al posto di $ {RULE}. :)

O si! Che Chi Analisi lsdi uscita?

Bene, quindi, dato tutto ciò ... penso sia abbastanza chiaro che questa regola è buona. Prima di tutto, la vera regola deve essere intesa come idiomatica, come spiegato sopra ...

Inoltre, non è solo necessario essere molto bravi con gli script di shell per sapere se possono essere risolti, in alcuni casi particolari. Inoltre, ci vuole altrettanto abilità per dirti che hai sbagliato quando stai provando a romperlo nei test! E dico con fiducia che una grande maggioranza del probabile pubblico di tali articoli (dando consigli come «Non analizzare l'output di ls!») Non può fare queste cose , e quelli che hanno tale abilità probabilmente si renderanno conto che lo capiscono da soli e ignorano comunque la regola.

Ma ... guarda questa domanda e come anche le persone che probabilmente hanno l'abilità hanno pensato che fosse una brutta chiamata farlo; e quanto sforzo ha speso l'autore della domanda per arrivare al punto del miglior esempio attuale! Vi garantisco che un problema è che il 99% delle persone là fuori avrebbe sbagliato e con risultati potenzialmente molto negativi! Anche se il metodo che viene deciso risulta essere buono; fino a quando (o un'altra) lsidea di analisi non viene adottata dall'IT / dalla gente dello sviluppatore nel suo insieme, resiste a molti test (in particolare la prova del tempo) e, infine, riesce a passare a uno stato di "tecnica comune", è probabile che un molte persone potrebbero provarlo e sbagliare ... con conseguenze disastrose.

Quindi, io ribadisco per l'ultima volta .... che, soprattutto in questo caso , che è per questo che " mai il parsing lsdi uscita!" è decisamente il modo giusto per esprimerlo.

[AGGIORNAMENTO 2014-05-18: chiarimento del ragionamento per la risposta (sopra) per rispondere a un commento del PO; la seguente aggiunta è in risposta alle aggiunte del PO alla domanda di ieri]

[AGGIORNAMENTO 2014-11-10: aggiunte intestazioni e contenuto riorganizzato / refactored; e anche: riformattazione, riformulazione, chiarimento e um ... "conciso-ifying" ... intendevo che questo fosse semplicemente un ripulito, sebbene si sia trasformato in un po 'di rilavorazione. l'avevo lasciato in uno stato dispiaciuto, quindi ho cercato principalmente di dargli un po 'di ordine. ho sentito che era importante lasciare in gran parte intatta la prima sezione; quindi solo due piccoli cambiamenti lì, ridondanti "ma" rimossi, e "quello" enfatizzato.]

† Inizialmente intendevo questo esclusivamente come un chiarimento sul mio originale; ma ho deciso di aggiungere altri elementi dopo aver riflettuto

‡ consultare https://unix.stackexchange.com/tour per le linee guida sui post


2
Non è mai idiomatico. Questa non è una risposta a nulla.
Mikeserv,

1
Hmm. Bene, non sapevo se questa risposta sarebbe stata soddisfacente, ma non mi aspettavo assolutamente che fosse controversa . E non volevo (intendevo) sostenere che "mai" fosse di per sé idiomatico; ma che "Non fare mai X!" è un uso idiomatico . Vedo due casi generali che possono mostrare che "Mai / non analizzare ls!" è un consiglio corretto: 1. dimostrare (con soddisfazione) che ogni caso d'uso in cui si potrebbe analizzare l' lsoutput ha un'altra soluzione disponibile, superiore in qualche modo, senza farlo. 2. dimostrare che, nei casi citati, l'affermazione non è letterale.
Shelleybutterfly,

Guardando di nuovo la tua domanda, vedo che hai prima menzionato "non ..." piuttosto che "mai ..." che è bene nella tua analisi, quindi chiarirò anche su questo punto. A questo punto c'è già una soluzione del primo tipo, che è apparentemente dimostrata / spiegata con tua soddisfazione, quindi non approfondirò molto. Ma cercherò di chiarire un po 'la mia risposta: come ho detto, non stavo cercando di essere controverso (o conflittuale!) Ma di sottolineare come queste affermazioni sono generalmente intese.
shelleybutterfly,

1
Dovrei ripulire quel post. Eppure, non è non è il modo giusto per frase esso. E 'un po' ridicolo che la gente pensa theyre qualificati per dire agli altri mai o dont - semplicemente dire loro che non credo che funzionerà e perché, ma so che cosa funziona e perché. lsè un'utilità per computer: è possibile analizzare l'output del computer.
Mikeserv,

1
Bene, ho invertito il mio downvote perché, per lo meno, hai ragione sulla cosa in bandiera. Cercherò di ripulirlo stasera o domani. Il mio pensiero è che sposterò la maggior parte degli esempi di codice in una risposta, immagino. Ma ancora, per quanto mi riguarda, non scusa le inesattezze in quel post di blog spesso citato. Vorrei che la gente smettesse di citare del tutto il manuale di bash - almeno non fino a quando non avessero citato le specifiche POSIX ...
mikeserv,

16

È possibile analizzare l'output di lsin alcuni casi? Sicuro. L'idea di estrarre un elenco di numeri di inode da una directory è un buon esempio: se sai che la tua implementazione lssupporta -q, e quindi ogni file produrrà esattamente una riga di output, e tutto ciò di cui hai bisogno sono i numeri di inode, analizzandoli da ls -Rai1ql'output è certamente una possibile soluzione. Ovviamente, se l'autore non avesse mai visto consigli come "Mai analizzare l'output di ls" prima, probabilmente non avrebbe pensato ai nomi dei file con nuove righe in essi, e probabilmente avrebbe lasciato fuori la 'q' di conseguenza, e il il codice sarebbe sottilmente rotto in quel caso limite - quindi, anche nei casi in cui l' lsoutput dell'analisi è ragionevole, questo consiglio è ancora utile.

Il punto più ampio è che, quando un novizio di shell script cerca di avere una figura di script fuori (per esempio) che cosa è il file più grande in una directory, o che cosa è il file più recentemente modificato in una directory, il suo primo istinto è quello di analizzare ls's output - comprensibile, perché lsè uno dei primi comandi che un principiante impara.

Sfortunatamente, quell'istinto è sbagliato e quell'approccio è rotto. Ancora più sfortunatamente, è sottilmente rotto - funzionerà la maggior parte del tempo, ma fallirà in casi limite che potrebbero essere sfruttati da qualcuno con conoscenza del codice.

Il principiante potrebbe pensare ls -s | sort -n | tail -n 1 | awk '{print $2}'a un modo per ottenere il file più grande in una directory. E funziona, fino a quando non hai un file con uno spazio nel nome.

OK, allora che ne dici ls -s | sort -n | tail -n 1 | sed 's/[^ ]* *[0-9]* *//'? Funziona bene fino a quando non hai un file con una nuova riga nel nome.

L'aggiunta -qagli lsargomenti di 'aiuta quando c'è una nuova riga nel nome del file? Potrebbe sembrare così, fino a quando non hai 2 file diversi che contengono un carattere non stampabile nello stesso punto del nome file, e quindi lsl'output non ti consente di distinguere quale di questi fosse più grande. Peggio ancora, al fine di espandere il "?", Probabilmente ricorre alla sua shell, il evalche causerà problemi se colpisce un file chiamato, ad esempio,

foo`/tmp/malicious_script`bar

Does --quoting-style=shellaiutare (se il vostro lssupporta anche esso)? No, viene ancora visualizzato? per i personaggi non stampabili, quindi è ancora ambiguo quale delle partite multiple sia stata la più grande. --quoting-style=literal? No, lo stesso. --quoting-style=localeo --quoting-style=cpotrebbe essere d'aiuto se hai solo bisogno di stampare il nome del file più grande in modo inequivocabile, ma probabilmente non se dovessi fare qualcosa con il file in seguito - sarebbe un mucchio di codice per annullare la quotazione e tornare al vero nome file in modo da che puoi passare a, diciamo, gzip.

E alla fine di tutto quel lavoro, anche se ciò che ha è sicuro e corretto per tutti i possibili nomi di file, è illeggibile e non realizzabile, e avrebbe potuto essere fatto molto più facilmente, in modo sicuro e leggibile in pitone, perl o ruby.

O anche usando altri strumenti shell - dalla parte superiore della mia testa, penso che questo dovrebbe fare il trucco:

find . -type f -printf "%s %f\0" | sort -nz | awk 'BEGIN{RS="\0"} END{sub(/[0-9]* /, "", $0); print}'

E dovrebbe essere almeno portatile come lo --quoting-styleè.


Oh vero sulla dimensione - probabilmente potrei farlo se ci provassi - dovrei? Im po 'stanco o tutta questa cosa - Mi piace la tua risposta, perché tu non dire non può o dont o mai , ma in realtà fornire esempi di forse perché non e comparabili in quale altro modo - grazie.
Mikeserv,

Penso che se ci provassi, scopriresti che è molto più difficile di quanto pensi. Quindi sì, consiglierei di provare. Sarò felice di continuare a dare nomi di file che si spezzeranno per te finché riuscirò a pensarli. :)
godlygeek

I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
terdon

@mikeserv e godlygeek, ho spostato questo thread di commenti per chattare . Per favore, non fare lunghe discussioni come questa nei commenti, ecco a cosa serve la chat.
terdon
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.