Glob com ordem numérica


28

Eu tenho esta lista de arquivos pdf em um diretório:

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

Quero concatená-los usando o ghostscript em ordem numérica (semelhante a isso):

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

Mas a ordem de expansão do shell não reproduz a ordem natural dos números, mas a ordem alfabética:

$ 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

Como posso obter a ordem desejada na expansão (se possível sem adicionar manualmente 0padding aos números nos nomes dos arquivos)?

Encontrei sugestões para usar ls | sort -V, mas não consegui fazê-lo funcionar no meu caso de uso específico.


Você pode usar apenas números de dois dígitos em todos os casos, para que a ordem alfabética corresponda à ordem numérica. A menos que você queira fazer as coisas da maneira mais difícil.
Curinga

11
Números de 3 dígitos, pelo menos! Lembre-se do Y2K.
waltinator

Respostas:


12

Dependendo do seu ambiente, você pode usar ls -vcom o GNU coreutils, por exemplo:

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

Ou se você estiver em versões recentes do FreeBSD ou OpenBSD:

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

ls -vvontade natural sort of (version) numbers within textpara que possa ser usado também ...
Sundeep

@ Sundeep: De fato, mas esta parece ser uma solução apenas para o coreUtil GNU.
Thor


11
@ Sundeep: O -Vrecurso de sorttambém não é especificado pelo POSIX. No entanto, parece ter se espalhado mais, por exemplo, tanto o FreeBSD quanto o OpenBSD o sortsuportam.
Thor

oh ok, você pode adicionar esses detalhes para responder também? Me deparei com essa resposta enquanto procurava por um problema semelhante (glob em ordem numérica) e, vendo lsusado, verifiquei se ela tinha opção por si só em vez de canalizar para classificar :)
Sundeep


12

Se todos os arquivos em questão tiverem o mesmo prefixo (ou seja, o texto antes do número; cnesse caso), você poderá usar

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

c?.pdfse expande para c0.pdf c1.pdf... c9.pdfc??.pdfexpande para c10.pdf c11.pdfc20.pdf (e até c99.pdf, conforme aplicável). Enquanto cada palavra da linha de comando que contém caracteres de expansão de nome de caminho é expandida para uma lista de nomes de arquivos classificados (agrupados) de acordo com a LC_COLLATEvariável, as listas resultantes da expansão de curingas adjacentes (globs) não são mescladas; eles são simplesmente concatenados. (Lembro-me de que a página de manual do shell já declarou isso explicitamente, mas não consigo encontrá-lo agora.)

Obviamente, se os arquivos puderem subir c999.pdf, você deve usá-lo c?.pdf c??.pdf c???.pdf. É certo que isso pode ser entediante se você tiver muitos dígitos. Você pode abreviar um pouco; por exemplo, para (até) cinco dígitos, você pode usar c?{,?{,?{,?{,?}}}}.pdf. Se sua lista de nomes de arquivos for esparsa (por exemplo, há um c0.pdfe umc12345.pdf , mas não necessariamente todos os números intermediários), você provavelmente deve definir a nullglobopção. Caso contrário, se (por exemplo) você não tiver arquivos com números de dois dígitos, obteria um c??.pdfargumento literal passado para o seu programa.

Se você tem vários prefixos (por exemplo, , , e , com os números de um ou dois dígitos), você pode usar a abordagem de força óbvia, bruta:a<number>.pdfb<number>.pdf c<number>.pdf

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

ou reduza para {a,b,c}?{,?}.pdf.


11
Esta é a melhor resposta, porque isso é para além de quaisquer reivindicações de uso esboçado de ls, statou qualquer outra coisa; e também funciona no bash, conforme solicitado.
Kyle

5

Se não houver lacunas , o seguinte pode ser útil (embora superficial e pouco robusto em relação aos casos extremos e à generalidade) - apenas para se ter uma idéia:

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

Se não pode ser lacunas, algumas [ -f c${i}.pdf ]cheque pode ser adicionado.

Editar também veja esta resposta , de acordo com a qual você pode (usando o Bash) usar

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

Geralmente, é uma boa idéia citar as referências de variáveis ​​do shell (por exemplo, "$FILES"e "$i"), a menos que você tenha um bom motivo para não fazê-lo e tenha certeza de que sabe o que está fazendo. (Por outro lado, enquanto chaves podem ser importantes, elas não são tão importantes quanto aspas, portanto, por exemplo, "c$i.pdf"é bom o suficiente.) Um comando como , onde contém uma lista de arquivos separados por espaço, pode parecer um bom motivo para use sem citá-lo (porque não funcionará nesse contexto). … (Continua)gs  [ …args… ]  $FILES$FILES$FILES"$FILES"
G-Man diz 'Restabelecer Monica' em

(Continua) ... Mas veja Implicações de segurança de esquecer de citar uma variável nos shells bash / POSIX , em particular, minha resposta a ela , para obter notas sobre como lidar com variáveis ​​de várias palavras como matrizes no bash (por exemplo, FILES=("c0.pdf")e FILES+=("c$i.pdf")); também esta resposta , que usa a técnica que sugiro.
G-Man diz 'Reinstate Monica'

1

Apenas citando e corrigindo a resposta de Thor ... NUNCA analise ls!

Você pode usar sort -V(uma extensão não POSIX para classificar):

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

(para alguns comandos, aparentemente para gs é esse comando, você precisa de "./ " em vez de " " ... se um não funcionar, tente o outro)


11
A saída não analisa ls é porque ls exibe os nomes de arquivo separados por nova linha, enquanto nova é tão válida quanto qualquer outra em um nome de arquivo, mas aqui você está fazendo a mesma coisa statmas adicionando vários outros problemas (como problemas com o início de nomes de arquivos com -, problema se houver muitos arquivos, statsendo um comando não portátil). E como você usou o operador split + glob sem ajustar o IFS ou desativar os globs, ainda terá problemas com nomes de arquivos com caracteres de espaço ou tab ou curinga.
Stéphane Chazelas

Para usar o GNU de sort -Vmaneira confiável, você precisaria ${(z)"$(printf '%s\0' * | sort -zV)"}de zsh(embora zsh(n)exista uma classificação numérica) ou readarray -td '' files < <(printf '%s\0' * | sort -zV)de bash4.4+.
Stéphane Chazelas

@ StéphaneChazelas obrigado, e você está certo de que a nova linha pode ser uma preocupação, mas esse não é o único motivo para não analisar. E sim, eu era preguiçosa e também não adicionei. Mas eu deveria ter usado printf ... vou mudar isso.
Peter

por lssi só (ou seja, sem -l), quais são essas outras preocupações ? Observe que --não ajudaria em um arquivo chamado -.
Stéphane Chazelas

@ StéphaneChazelas existem outras diferenças entre as versões ... como algumas impressas "total 0" por lá, e as versões mais recentes do ls ainda colocam aspas em torno de coisas onde você não as quer ... touch \"test\"; ls -1por exemplo, mostra '"test"'no meu ls. Simplesmente não é para ser analisado ... é uma interface de usuário, não um comando de script.
Peter
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.