Glob con ordine numerico


28

Ho questo elenco di file pdf in una directory:

c0.pdf   c12.pdf  c15.pdf  c18.pdf  c20.pdf  c4.pdf  c7.pdf
c10.pdf  c13.pdf  c16.pdf  c19.pdf  c2.pdf   c5.pdf  c8.pdf
c11.pdf  c14.pdf  c17.pdf  c1.pdf   c3.pdf   c6.pdf  c9.pdf

Voglio concatenarli usando ghostscript in ordine numerico (simile a questo):

gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=out.pdf *.pdf

Ma l'ordine di espansione della shell non riproduce l'ordine naturale dei numeri ma l'ordine alfabetico:

$ for f in *.pdf; do echo $f; done
c0.pdf
c10.pdf
c11.pdf
c12.pdf
c13.pdf
c14.pdf
c15.pdf
c16.pdf
c17.pdf
c18.pdf
c19.pdf
c1.pdf
c20.pdf
c2.pdf
c3.pdf
c4.pdf
c5.pdf
c6.pdf
c7.pdf
c8.pdf
c9.pdf

Come posso ottenere l'ordine desiderato nell'espansione (se possibile senza aggiungere manualmente 0-pad ai numeri nei nomi dei file)?

Ho trovato suggerimenti da usare ls | sort -V , ma non sono riuscito a farlo funzionare per il mio caso d'uso specifico.


Si potrebbe semplicemente usare numeri a due cifre in tutti i casi, per cui l'ordine alfabetico corrisponderà l'ordine numerico. A meno che tu non voglia fare le cose nel modo più duro.
Wildcard il

1
Numeri di 3 cifre, almeno! Ricorda Y2K.
Waltinator,

Risposte:


12

A seconda del proprio ambiente è possibile utilizzare ls -vcon coreutils GNU, ad esempio:

gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
   -sOutputFile=out.pdf $(ls -v)

O se utilizzi versioni recenti di FreeBSD o OpenBSD:

gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
   -sOutputFile=out.pdf $(ls | sort -V)

ls -vsarà natural sort of (version) numbers within textcosì che può essere utilizzato anche ...
Sundeep

@Sundeep: In effetti, ma questa sembra essere una soluzione unica per coreutils GNU.
Thor,

sì, sembra specifico di GNU - pubs.opengroup.org/onlinepubs/9699919799
Sundeep

1
@Sundeep: la -Vfunzione di sortnon è nemmeno specificata da POSIX. Tuttavia, sembra essersi diffuso ulteriormente, ad esempio sia FreeBSD che OpenBSD lo sortsupportano.
Thor,

oh ok, puoi aggiungere anche questi dettagli per rispondere? Mi sono imbattuto in questa risposta durante la ricerca di un problema simile (glob in ordine numerico) e vedendo lsusato ho verificato se avesse l'opzione da solo invece di eseguire il piping per ordinare :)
Sundeep


12

Se tutti i file in questione hanno lo stesso prefisso (ovvero il testo prima del numero; c in questo caso), è possibile utilizzare

gs   ... args ...   c? .pdf c ??. pdf

c?.pdfsi espande a c0.pdf c1.pdf... c9.pdfc??.pdfsi espande in c10.pdf c11.pdf... c20.pdf (e fino a c99.pdf, a seconda dei casi). Mentre ogni parola della riga di comando contenente i caratteri di espansione del nome percorso viene espansa in un elenco di nomi di file ordinati (fascicolati) in base alla LC_COLLATEvariabile, gli elenchi risultanti dall'espansione di caratteri jolly (globs) adiacenti non vengono uniti; sono semplicemente concatenati. (Mi sembra di ricordare che la pagina man della shell una volta lo affermava esplicitamente, ma non riesco a trovarla ora.)

Ovviamente se i file possono arrivare a c999.pdf, dovresti usare c?.pdf c??.pdf c???.pdf. Certo, questo può diventare noioso se hai molte cifre. Puoi abbreviarlo un po '; ad esempio, per (fino a) cinque cifre, è possibile utilizzare c?{,?{,?{,?{,?}}}}.pdf. Se il tuo elenco di nomi di file è scarso (ad esempio, c'è un c0.pdfe un c12345.pdf, ma non necessariamente tutti i numeri in mezzo), probabilmente dovresti impostare ilnullglob opzione. Altrimenti, se (per esempio) non hai file con numeri a due cifre, otterrai un c??.pdfargomento letterale passato al tuo programma.

Se si dispone di più prefissi (ad esempio, , , e , con i numeri di una o due cifre), è possibile utilizzare l'ovvio, approccio forza bruta:a<number>.pdfb<number>.pdf c<number>.pdf

a?.pdf a??.pdf b?.pdf b??.pdf c?.pdf c??.pdf

o comprimilo in {a,b,c}?{,?}.pdf.


1
Questa è la risposta migliore, perché è al di là di eventuali richieste di impiego abbozzato di ls, stato qualsiasi altra cosa; e funziona anche in bash come richiesto.
Kyle

5

Se non ci sono lacune , quanto segue potrebbe rivelarsi utile (seppur impreciso e non solido riguardo ai casi limite e alla generalità) - solo per avere un'idea:

FILES="c0.pdf"
for i in $(seq 1 20); do FILES="${FILES} c${i}.pdf"; done
gs [...args...] $FILES

Se potrebbero esserci delle lacune, [ -f c${i}.pdf ]potrebbe essere aggiunto qualche controllo.

Modifica anche vedere questa risposta , secondo la quale potresti (usando Bash) usare

gs [..args..] c{1..20}.pdf

È generalmente una buona idea citare i riferimenti alle variabili della shell (ad esempio, "$FILES"e "$i") a meno che non si abbia una buona ragione per non farlo e si è sicuri di sapere cosa si sta facendo. (Al contrario, sebbene le parentesi graffe possano essere importanti, non sono importanti quanto le virgolette, quindi, per esempio, "c$i.pdf"è abbastanza buono.) Un comando come , dove contiene un elenco di file separato da spazi, può sembrare un buon motivo per utilizzare senza virgolette (perché non funzionerà in quel contesto). ... (proseguendo)gs  [ …args… ]  $FILES$FILES$FILES"$FILES"
G-Man dice "Ripristina Monica" il

(Proseguendo) ... Ma vedi le implicazioni sulla sicurezza di dimenticare di citare una variabile nelle shell bash / POSIX , in particolare, la mia risposta ad essa , per le note su come gestire le variabili multi-parola come array in bash (ad esempio, FILES=("c0.pdf")e FILES+=("c$i.pdf")); anche questa risposta , che utilizza la tecnica che suggerisco.
G-Man dice 'Reinstate Monica' il

1

Sto solo citando e risolvendo la risposta di Thor ... MAI analizzare!

È possibile utilizzare sort -V(un'estensione non POSIX per ordinare):

printf '%s\0' ./* | sort -zV \
    | xargs -0 gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH \
        -sDEVICE=pdfwrite -sOutputFile=out.pdf

(per alcuni comandi, apparentemente per gs è un tale comando, hai bisogno di "./ " invece di " " ... se uno non funziona, prova l'altro)


1
L' output non analizzare ls è perché ls mostra i nomi dei file separati da newline mentre newline è valido come qualsiasi in un nome di file, ma qui stai facendo la stessa cosa con l' stataggiunta di molti altri problemi (come problemi con l'avvio dei nomi dei file con -, problema se ci sono troppi file, statessendo un comando non portatile). E poiché hai utilizzato l'operatore split + glob senza regolare IFS o disabilitare globs, avrai comunque problemi con nomi di file con spazio o tab o caratteri jolly.
Stéphane Chazelas,

Per usare GNU in sort -Vmodo affidabile, avresti bisogno ${(z)"$(printf '%s\0' * | sort -zV)"}di zsh(sebbene lo zshabbia (n)già per l'ordinamento numerico) o readarray -td '' files < <(printf '%s\0' * | sort -zV)in bash4.4+.
Stéphane Chazelas,

@Grazie a StéphaneChazelas, e hai ragione che Newline può essere un problema, ma non è l'unica ragione per non analizzare ls. E sì, ero pigro e non ho aggiunto neanche. Ma avrei dovuto usare printf ... lo cambierò.
Peter,

da lssolo (cioè senza -l), quali sono queste altre preoccupazioni ? Nota che --non sarebbe d'aiuto per un file chiamato -.
Stéphane Chazelas,

@ StéphaneChazelas ci sono altre differenze tra le versioni ... come alcune stampe "total 0" su di esse, e le versioni più recenti di ls persino attaccano le virgolette intorno a cose dove non le vuoi ... touch \"test\"; ls -1per esempio mostra '"test"'sul mio ls. Semplicemente non è pensato per essere analizzato ... è un'interfaccia utente, non un comando di scripting.
Peter,
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.