File glob * senza * utilizzare una shell

Aug 22 2020

Voglio elencare i file in una determinata sottodirectory, ma lo faccio come parte di un docker execcontenitore all'interno di un docker, quindi non voglio preoccuparmi di avviare una shell di cui non ho davvero bisogno. È possibile trovare tutte le corrispondenze per un glob con un semplice strumento da riga di comando e non solo una shell?

Ad esempio, la mia attuale chiamata è bash -l -c 'echo /usr/local/conda-meta/*.json'. È possibile semplificarlo utilizzando uno strumento comunemente disponibile, ottenendo qualcosa di simile globber /usr/local/conda-meta/*.json, che sarebbe molto più semplice e leggero?

Risposte

16 StéphaneChazelas Aug 23 2020 at 05:12

shè semplice e comunemente disponibile. shè lo strumento invocato per analizzare le righe di comando in cose come system(cmdline)in molte lingue. Molti sistemi operativi, inclusi alcuni GNU, hanno smesso di utilizzare bash(la shell GNU) per l'implementazione shperché è diventato troppo gonfio per fare proprio quella semplice cosa di analizzare le righe di comando e interpretare gli shscript POSIX .

La tua bash -l -c 'echo /usr/local/conda-meta/*.json'riga di comando potrebbe essere già interpretata da una shchiamata. Quindi forse puoi semplicemente fare:

printf '%s\n' /usr/local/conda-meta/*.json

direttamente. Altrimenti:

sh -c 'printf "%s\n" /usr/local/conda-meta/*.json'

Puoi anche usare findqui. findnon esegue il globbing ma può riportare nomi di file che corrispondono a modelli simili a quelli della shell.

LC_ALL=C find /usr/local/conda-meta/. ! -name . -prune -name '*.json'

O con alcune findimplementazioni:

LC_ALL=C find /usr/local/conda-meta -mindepth 1 -maxdepth 1 -name '*.json'

(nota che il LC_ALL=Cnecessario qui in modo che *corrisponda a qualsiasi sequenza di byte, non solo quelli che formano caratteri validi nella localizzazione corrente, è un costrutto di shell. Se quella riga di comando non viene interpretata da una shell, potrebbe essere necessario cambiarla in env LC_ALL=C find...)

Alcune differenze con i glob di shell:

  • l'elenco dei file non è ordinato
  • i file nascosti sono inclusi (potresti aggiungere un ! -name '.*'per escluderli)
  • non si ottiene alcun output se non esiste un file corrispondente. i glob hanno quella caratteristica sbagliata che lasciano il modello così com'è non espanso in quel caso.
  • con la prima variante (standard), i file verranno emessi come /usr/local/conda-meta/./file.json.
  • alcuni glob come x*/y/../*znon sono facilmente traducibili (si noti anche il diverso comportamento rispetto ai collegamenti simbolici alle directory in quel caso).

In ogni caso, non è possibile utilizzare echoper produrre dati arbitrari.

La mia prossima domanda sarebbe: cosa farai con quell'output? Con echo, stai emettendo quei percorsi di file separati da caratteri SPC e con my printfo findsuperiore, delimitato da caratteri NL. Entrambi NLe SPCsono caratteri perfettamente validi nei nomi di file, quindi quegli output non sono affidabili post-elaborabili. Si potrebbe utilizzare '%s\0'al posto di '%s\n'(o usare finds' -print0se supportato), non adatto per la visualizzazione a un utente, ma post-lavorabile.

In termini di efficienza, confrontando Ubuntu 20.04 /bin/sh(trattino 0.5.10.2) con il suo find(GNU find4.7.0).

Tempo di avvio:

$ time (repeat 1000 sh -c '') ( repeat 1000; do; sh -c ''; done; ) 0.91s user 0.66s system 105% cpu 1.483 total $ time (repeat 1000 find . -quit)
( repeat 1000; do; find . -quit; done; )  1.35s user 1.25s system 103% cpu 2.507 total

Globbing di alcuni jsonfile:

$ TIMEFMT='%U user %S system %P cpu %*E total' $ time (repeat 1000 sh -c 'printf "%s\n" /usr/share/iso-codes/json/*.json') > /dev/null
0.95s user 0.72s system 105% cpu 1.587 total
$ time (repeat 1000  find /usr/share/iso-codes/json -mindepth 1 -maxdepth 1 -name '*.json') > /dev/null
1.34s user 1.35s system 103% cpu 2.599 total

Anche bashnon è certo più lento di findqui:

$ time (repeat 1000 bash -c 'printf "%s\n" /usr/share/iso-codes/json/*.json') > /dev/null
1.53s user 1.36s system 102% cpu 2.808 total

Ovviamente YMMV dipende dal sistema, dall'implementazione, dalla versione delle rispettive utilità e dalle librerie a cui sono collegati.

Ora, nella nota storica, il nome glob in realtà deriva dal nome di un'utilità chiamata globnelle primissime versioni di Unix nei primi anni '70. Si trovava in /etced è stato invocato da shcome aiuto per espandere i modelli di caratteri jolly.

Troverai alcuni progetti online per far rivivere quella vecchia shell come https://etsh.nl/. Più come un esercizio di archeologia, potresti costruire l' globutilità da lì e poi essere in grado di fare:

glob printf '%s\n' '/usr/local/conda-meta/*.json'

Alcune note di avvertimento però.

  • quelli sono globi antichi, [!x](figuriamoci [^x]) non è supportato.
  • non è sicuro a 8 bit. In realtà, l'8 ° bit viene utilizzato per sfuggire agli operatori glob ( $'\xe9*'corrisponderebbe alla stessa cosa di i*, $'\xaa*'corrisponderebbe ai nomi di file che iniziano con *; la shell imposterebbe quell'8 ° bit per i caratteri tra virgolette prima di invocare glob)
  • intervalli come la [a-f]corrispondenza sul valore di byte piuttosto che sull'ordine di confronto (in pratica, questo è generalmente un vantaggio IMO).
  • I glob non corrispondenti danno luogo a un No matcherrore (di nuovo, probabilmente preferibilmente, è qualcosa che è stato rotto dalla shell Bourne alla fine degli anni '70).

La globfunzionalità è stata successivamente spostata nella shell a partire dalla shell PWB e dalla shell Bourne alla fine degli anni '70. Successivamente, alcune fnmatch()e glob()funzioni sono state aggiunte alla libreria C per consentire a quella caratteristica di essere utilizzata da altre applicazioni, ma non sono a conoscenza di un'utilità standard né comune che sia un'interfaccia semplice per quella funzione. Anche perlusato per invocare cshnei suoi primi giorni per espandere i pattern glob.

7 BasileStarynkevitch Aug 23 2020 at 14:50

File glob senza utilizzare una shell

La documentazione ovvia da leggere è glob (7) .

Puoi scrivere o usare un programma C chiamando fnmatch (3) , glob (3) , nftw (3) , stat (2) , readdir (3)

Se scrivi in Guile , Python , Go , Rust , Ocaml , Common Lisp (es. SBCL ) ... troverai funzioni simili. Con C ++ guarda in POCO e Qt .

Suppongo che tu stia usando un sistema Linux. A proposito, la mia shell interattiva è zsh (le cui funzionalità di completamento automatico sono preferibili IMHO).