Glob avec ordre numérique

28

J'ai cette liste de fichiers pdf dans un répertoire:

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

Je veux les concaténer en utilisant ghostscript dans l'ordre numérique (similaire à ceci):

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

Mais l'ordre d'expansion du shell ne reproduit pas l'ordre naturel des nombres mais l'ordre alphabétique:

$ 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

Comment puis-je obtenir l'ordre souhaité dans l'extension (si possible sans ajouter manuellement 0-padding aux numéros dans les noms de fichiers)?

J'ai trouvé des suggestions à utiliser ls | sort -V, mais je n'ai pas pu le faire fonctionner pour mon cas d'utilisation spécifique.

moooeeeep
la source
Vous pouvez simplement utiliser des nombres à deux chiffres dans tous les cas, donc l'ordre alphabétique correspondra à l'ordre numérique. À moins que vous ne vouliez faire les choses à la dure.
Wildcard
1
Des nombres à 3 chiffres, au moins! Rappelez-vous Y2K.
waltinator

Réponses:

12

Selon votre environnement, vous pouvez utiliser ls -v avec des coreutils GNU, par exemple:

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

Ou si vous utilisez des versions récentes de FreeBSD ou OpenBSD:

gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
   -sOutputFile=out.pdf $(ls | sort -V)
Thor
la source
ls -vsera natural sort of (version) numbers within textdonc utilisable aussi ...
Sundeep
@Sundeep: En effet, mais cela semble être une seule solution GNU coreutils.
Thor
oui, semble être spécifique à GNU - pubs.opengroup.org/onlinepubs/9699919799
Sundeep
1
@Sundeep: La -Vfonctionnalité de sortn'est pas non plus spécifiée par POSIX. Cependant, il semble s'être propagé plus loin, par exemple, FreeBSD et OpenBSD le sortsupportent.
Thor
oh ok, pouvez-vous également ajouter ces détails pour répondre? Je suis tombé sur cette réponse lors de la recherche d'un problème similaire (glob dans l'ordre numérique) et en voyant lsutilisé, j'ai vérifié s'il avait une option par lui-même au lieu de
piper
23

Une fois de plus, les qualificatifs glob de zsh viennent à la rescousse.

echo *.pdf(n)
Gilles 'SO- arrête d'être méchant'
la source
12

Si tous les fichiers en question ont le même préfixe (c'est-à-dire le texte avant le numéro; c dans ce cas), vous pouvez utiliser

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

c?.pdfs'étend à c0.pdf c1.pdfc9.pdfc??.pdfs'étend à c10.pdf c11.pdfc20.pdf (et jusqu'à c99.pdf, selon le cas). Bien que chaque mot de ligne de commande contenant des caractères d'extension de chemin d'accès soit développé en une liste de noms de fichiers triés (assemblés) conformément à la LC_COLLATEvariable, les listes résultant de l'expansion des caractères génériques adjacents (globs) ne sont pas fusionnées; ils sont simplement concaténés. (Il semble que je me souvienne que la page de manuel du shell l'a déjà dit explicitement, mais je ne le trouve pas maintenant.)

Bien sûr, si les fichiers peuvent monter c999.pdf, vous devez utiliser c?.pdf c??.pdf c???.pdf. Certes, cela peut devenir fastidieux si vous avez beaucoup de chiffres. Vous pouvez l'abréger un peu; par exemple, pour (jusqu'à) cinq chiffres, vous pouvez utiliser c?{,?{,?{,?{,?}}}}.pdf. Si votre liste de noms de fichiers est rare (par exemple, il y a un c0.pdfet un c12345.pdf, mais pas nécessairement tous les nombres entre les deux), vous devriez probablement définir l' nullgloboption. Sinon, si (par exemple) vous n'avez pas de fichiers avec des nombres à deux chiffres, vous obtiendrez un littéralc??.pdf argument passé à votre programme.

Si vous avez plusieurs préfixes (par exemple , et , avec des chiffres d'un ou deux chiffres), vous pouvez utiliser l'approche de la force évidente, brute:a<number>.pdfb<number>.pdf c<number>.pdf

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

ou le réduire {a,b,c}?{,?}.pdf.

G-Man dit «Réintègre Monica»
la source
1
Ceci est la meilleure réponse car il est au - delà de toute réclamation d'utilisation sommaire de ls, statou toute autre chose; et fonctionne également en bash comme demandé.
Kyle
5

S'il n'y a pas de lacunes , les éléments suivants pourraient s'avérer utiles (bien que sommaires et peu robustes concernant les cas marginaux et la généralité) - juste pour avoir une idée:

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

S'il peut y avoir des lacunes, certains[ -f c${i}.pdf ] vérification pourrait être ajoutée.

Modifier également voir cette réponse , selon laquelle vous pourriez (en utilisant Bash) utiliser

gs [..args..] c{1..20}.pdf
sr_
la source
C'est généralement une bonne idée de citer vos références de variables shell (par exemple, "$FILES"et "$i") à moins que vous n'ayez une bonne raison de ne pas le faire et que vous êtes sûr de savoir ce que vous faites. (En revanche, si les accolades peuvent être importantes, elles ne sont pas aussi importantes que les guillemets, donc, par exemple, elles "c$i.pdf"sont suffisantes.) Une commande comme , où contient une liste de fichiers séparés par des espaces, peut sembler une bonne raison de utiliser sans le citer (car ne fonctionnera pas dans ce contexte). … (Suite)gs  [ …args… ]  $FILES$FILES$FILES"$FILES"
G-Man dit «Réintègre Monica»
(Suite)… Mais voir les implications pour la sécurité d'oublier de citer une variable dans les shells bash / POSIX , en particulier, ma réponse , pour des notes sur la façon de gérer les variables multi-mots comme des tableaux dans bash (par exemple, FILES=("c0.pdf")et FILES+=("c$i.pdf")); aussi cette réponse , qui utilise la technique que je suggère.
G-Man dit `` Réintègre Monica ''
1

Je ne fais que citer et corriger la réponse de Thor ... NE JAMAIS analyser ls!

Vous pouvez utiliser sort -V(une extension non POSIX pour trier):

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

(pour certaines commandes, apparemment pour gs est une telle commande, vous avez besoin de "./ " au lieu de " " ... si l'une ne fonctionne pas, essayez l'autre)

Peter
la source
1
La sortie de ne pas analyser ls est parce que ls affiche les noms de fichiers séparés par une nouvelle ligne tandis que la nouvelle ligne est aussi valide que n'importe quel autre dans un nom de fichier, mais ici vous faites la même chose avec statmais en ajoutant plusieurs autres problèmes (comme des problèmes avec le démarrage des noms de fichiers avec -, problème s'il y a trop de fichiers, statétant une commande non portable). Et parce que vous avez utilisé l'opérateur split + glob sans ajuster IFS ni désactiver les globs, vous aurez toujours des problèmes avec les noms de fichiers avec espace ou tabulation ou caractères génériques.
Stéphane Chazelas
Pour utiliser GNU de sort -Vmanière fiable, vous auriez besoin de ${(z)"$(printf '%s\0' * | sort -zV)"}in zsh(bien qu'il l' zshait (n)déjà pour le tri numérique) ou readarray -td '' files < <(printf '%s\0' * | sort -zV)in bash4.4+.
Stéphane Chazelas
@ StéphaneChazelas merci, et vous avez raison de dire que la nouvelle ligne peut être un problème, mais ce n'est pas la seule raison de ne pas analyser ls. Et oui, j'étais paresseux et je n'ai pas ajouté - non plus. Mais j'aurais dû utiliser printf ... Je vais changer ça.
Peter
pour lsseul (c'est-à-dire sans -l), quelles sont ces autres préoccupations ? Notez que --cela n'aiderait pas pour un fichier appelé -.
Stéphane Chazelas
@ StéphaneChazelas il y a d'autres différences entre les versions ... comme certains imprimer "total 0" là-bas, et les dernières versions ls collent même des guillemets autour des choses où vous ne les voulez pas ... touch \"test\"; ls -1par exemple montre '"test"'sur mon ls. Ce n'est tout simplement pas destiné à être analysé ... c'est une interface utilisateur, pas une commande de script.
Peter