File glob * senza * utilizzare una shell
Voglio elencare i file in una determinata sottodirectory, ma lo faccio come parte di un docker exec
contenitore 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
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 sh
perché è diventato troppo gonfio per fare proprio quella semplice cosa di analizzare le righe di comando e interpretare gli sh
script POSIX .
La tua bash -l -c 'echo /usr/local/conda-meta/*.json'
riga di comando potrebbe essere già interpretata da una sh
chiamata. 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 find
qui. find
non 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 find
implementazioni:
LC_ALL=C find /usr/local/conda-meta -mindepth 1 -maxdepth 1 -name '*.json'
(nota che il LC_ALL=C
necessario 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/../*z
non sono facilmente traducibili (si noti anche il diverso comportamento rispetto ai collegamenti simbolici alle directory in quel caso).
In ogni caso, non è possibile utilizzare echo
per 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 printf
o find
superiore, delimitato da caratteri NL. Entrambi NL
e SPC
sono 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 find
s' -print0
se 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 find
4.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 json
file:
$ 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 bash
non è certo più lento di find
qui:
$ 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 glob
nelle primissime versioni di Unix nei primi anni '70. Si trovava in /etc
ed è stato invocato da sh
come 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' glob
utilità 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 dii*
,$'\xaa*'
corrisponderebbe ai nomi di file che iniziano con*
; la shell imposterebbe quell'8 ° bit per i caratteri tra virgolette prima di invocareglob
) - 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 match
errore (di nuovo, probabilmente preferibilmente, è qualcosa che è stato rotto dalla shell Bourne alla fine degli anni '70).
La glob
funzionalità è 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 perl
usato per invocare csh
nei suoi primi giorni per espandere i pattern glob.
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).