Glob con orden numérico

28

Tengo esta lista de archivos pdf en un directorio:

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

Quiero concatenarlos usando ghostscript en orden numérico (similar a esto):

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

Pero el orden de expansión del shell no reproduce el orden natural de los números sino el orden alfabético:

$ 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

¿Cómo puedo lograr el orden deseado en la expansión (si es posible sin agregar manualmente 0-padding a los números en los nombres de archivo)?

Encontré sugerencias para usar ls | sort -V, pero no pude hacerlo funcionar para mi caso de uso específico.

moooeeeep
fuente
Usted podría simplemente utilizar números de dos dígitos en todos los casos, por lo que el orden alfabético coincidirá con el orden numérico. A menos que quieras hacer las cosas de la manera difícil.
Comodín el
1
¡Números de 3 dígitos, al menos! Recuerda Y2K.
waltinator

Respuestas:

12

Dependiendo de su entorno, puede usar ls -vcon GNU coreutils, por ejemplo:

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

O si tiene versiones recientes de FreeBSD u OpenBSD:

gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
   -sOutputFile=out.pdf $(ls | sort -V)
Thor
fuente
ls -vserá natural sort of (version) numbers within textasí que también se puede usar ...
Sundeep
@Sundeep: De hecho, pero esta parece ser una solución única de GNU coreutils.
Thor
sí, parece específico de GNU - pubs.opengroup.org/onlinepubs/9699919799
Sundeep
1
@Sundeep: POSIX tampoco especifica la -Vfunción de sort. Sin embargo, parece haberse extendido más, por ejemplo, tanto FreeBSD como OpenBSD lo sortadmiten.
Thor
oh ok, ¿puedes agregar estos detalles para responder también? Encontré esta respuesta mientras buscaba un problema similar (glob en orden numérico) y al verlo ls, verifiqué si tenía una opción por sí misma en lugar de tuberías para ordenar :)
Sundeep
12

Si todos los archivos en cuestión tienen el mismo prefijo (es decir, el texto antes del número; cen este caso), puede usar

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

c?.pdfse expande a c0.pdf c1.pdf... c9.pdfc??.pdfse expande a c10.pdf c11.pdf... c20.pdf (y hasta c99.pdf, según corresponda). Si bien cada palabra de línea de comando que contiene caracteres de expansión de nombre de ruta se expande a una lista de nombres de archivo ordenados (clasificados) de acuerdo con la LC_COLLATEvariable, las listas resultantes de la expansión de comodines adyacentes (globos) no se fusionan; simplemente están concatenados. (Me parece recordar que la página del manual de shell una vez declaró esto explícitamente, pero no puedo encontrarlo ahora).

Por supuesto, si los archivos pueden subir c999.pdf, deberías usarlos c?.pdf c??.pdf c???.pdf. Es cierto que esto puede volverse tedioso si tienes muchos dígitos. Puedes abreviarlo un poco; por ejemplo, para (hasta) cinco dígitos, puede usar c?{,?{,?{,?{,?}}}}.pdf. Si su lista de nombres de archivo es escasa (p. Ej., Hay una c0.pdfy una c12345.pdf, pero no necesariamente todos los números intermedios), probablemente debería configurar la nullglobopción. De lo contrario, si (por ejemplo) no tiene archivos con números de dos dígitos, obtendrá un c??.pdfargumento literal para su programa.

Si tiene varios prefijos (por ejemplo, , , y , con los números de uno o dos dígitos), se puede utilizar el método de fuerza obvia, bruta:a<number>.pdfb<number>.pdf c<number>.pdf

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

o colapsarlo {a,b,c}?{,?}.pdf.

G-Man dice 'restablecer a Mónica'
fuente
1
Esta es la mejor respuesta, ya que está más allá de cualquier reclamación de uso incompleto de ls, stato cualquier otra cosa; y también funciona en bash según lo solicitado.
Kyle
5

Si no hay brechas , lo siguiente podría ser útil (aunque incompleto y no robusto con respecto a casos extremos y generalidad), solo para tener una idea:

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

Si puede haber lagunas, se [ -f c${i}.pdf ]podría agregar alguna verificación.

Editar también vea esta respuesta , según la cual podría (usando Bash) usar

gs [..args..] c{1..20}.pdf
sr_
fuente
Por lo general, es una buena idea citar las referencias de variables de shell (por ejemplo, "$FILES"y "$i") a menos que tenga una buena razón para no hacerlo y esté seguro de saber lo que está haciendo. (Por el contrario, aunque las llaves pueden ser importantes, no son tan importantes como las comillas, por lo que, por ejemplo, "c$i.pdf"es lo suficientemente bueno). Un comando comogs  [ …args… ]  $FILES , donde $FILEScontiene una lista de archivos separados por espacios, puede parecer una buena razón para use $FILESsin citarlo (porque "$FILES"no funcionará en ese contexto). … (Continúa)
G-Man dice 'Restablecer a Monica' el
(Continúa) ... Pero mira implicaciones de seguridad de olvidarse de citar una variable en bash / POSIX shells , en particular, mi respuesta , para obtener notas sobre cómo manejar variables de varias palabras como matrices en bash (por ejemplo, FILES=("c0.pdf")y FILES+=("c$i.pdf")); También esta respuesta , que utiliza la técnica que sugiero.
G-Man dice 'reinstalar a Monica' el
1

Solo citando y arreglando la respuesta de Thor ... ¡NUNCA analices ls!

Puede usar sort -V(una extensión que no sea POSIX para ordenar):

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

(para algunos comandos, aparentemente para gs es un comando así, necesita "./ " en lugar de " " ... si uno no funciona, intente con el otro)

Peter
fuente
1
El resultado de no analizar ls se debe a que ls muestra los nombres de archivo separados por una nueva línea, mientras que la nueva línea es tan válida como cualquier otra en un nombre de archivo, pero aquí está haciendo lo mismo statpero agregando varios otros problemas (como problemas con los nombres de archivos que comienzan con -, problema si hay demasiados archivos, statsiendo un comando no portátil). Y debido a que usó el operador split + glob sin ajustar IFS o deshabilitar glob, aún tendrá problemas con los nombres de archivo con espacio o tabulación o caracteres comodín.
Stéphane Chazelas
Para usar GNU de sort -Vmanera confiable, necesitaría ${(z)"$(printf '%s\0' * | sort -zV)"}in zsh(aunque ya zshtiene (n)para la clasificación numérica) o readarray -td '' files < <(printf '%s\0' * | sort -zV)in bash4.4+.
Stéphane Chazelas
@ StéphaneChazelas gracias, y tiene razón en que la nueva línea puede ser una preocupación, pero esa no es la única razón para no analizar ls. Y sí, era vago y tampoco agregué nada. Pero debería haber usado printf ... Cambiaré eso.
Peter
por lssí solo (es decir, sin -l), ¿cuáles son esas otras preocupaciones ? Tenga en cuenta que --no ayudaría para un archivo llamado -.
Stéphane Chazelas
@ StéphaneChazelas hay otras diferencias entre las versiones ... como algunos imprimen "total 0" allí, y las versiones más recientes de ls incluso pegan citas alrededor de cosas donde no las quieres ... touch \"test\"; ls -1por ejemplo, se muestran '"test"'en mi ls. Simplemente no está destinado a ser analizado ... es una interfaz de usuario, no un comando de secuencias de comandos.
Peter