Glob z porządkiem numerycznym

28

Mam tę listę plików pdf w katalogu:

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

Chcę połączyć je za pomocą ghostscript w kolejności numerycznej (podobnej do tej):

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

Ale kolejność rozszerzania powłoki nie odtwarza naturalnego porządku liczb, ale kolejność alfabetyczną:

$ 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

Jak mogę osiągnąć pożądaną kolejność w rozwinięciu (jeśli to możliwe bez ręcznego dodawania 0-paddingu do liczb w nazwach plików)?

Znalazłem sugestie do użycia ls | sort -V, ale nie mogłem zmusić go do działania w moim konkretnym przypadku użycia.

moooeeeep
źródło
Państwo mogłoby wystarczy użyć dwucyfrowych liczb we wszystkich przypadkach, więc kolejności alfabetycznej dopasuje kolejności numerycznej. Chyba że chcesz robić rzeczy w trudny sposób.
Wildcard,
1
Przynajmniej 3 cyfry! Pamiętaj Y2K.
waltinator

Odpowiedzi:

12

W zależności od środowiska możesz używać ls -vz GNU coreutils, np .:

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

Lub jeśli korzystasz z najnowszych wersji FreeBSD lub OpenBSD:

gs -q -sPAPERSIZE=a4 -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
   -sOutputFile=out.pdf $(ls | sort -V)
Thor
źródło
ls -vbędzie natural sort of (version) numbers within texttak, że mogą być stosowane również ...
Sundeep
@ Sundeep: Rzeczywiście, ale wydaje się, że jest to rozwiązanie oparte tylko na jądrach GNU.
Thor
tak, wygląda na to, że jest specyficzne dla GNU - pubs.opengroup.org/onlinepubs/9699919799
Sundeep
1
@ Sundeep: -VFunkcja sortPOSIX również nie jest określona. Wydaje się jednak, że rozprzestrzenił się dalej, na przykład sortobsługuje go zarówno FreeBSD, jak i OpenBSD .
Thor
o ok, czy możesz dodać te szczegóły, aby również odpowiedzieć? Natknąłem się na tę odpowiedź, szukając podobnego problemu (glob w porządku numerycznym) i widząc lsużywane, sprawdziłem, czy ma opcję samą w sobie zamiast
potokowania
23

Raz jeszcze na ratunek przybywają kwalifikatory globalne zsh .

echo *.pdf(n)
Gilles „SO- przestań być zły”
źródło
12

Jeśli wszystkie pliki mają ten sam prefiks (tj. Tekst przed liczbą; cw tym przypadku), możesz użyć

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

c?.pdfrozszerza się do c0.pdf c1.pdf... c9.pdfc??.pdfrozwija się do c10.pdf c11.pdfc20.pdf (i do c99.pdf, zależnie od przypadku). Chociaż każde słowo wiersza polecenia zawierające znak (i) rozwijające nazwę ścieżki jest rozwijane do listy nazw plików posortowanych (zestawionych) zgodnie ze LC_COLLATEzmienną, listy wynikające z rozwinięcia sąsiednich symboli wieloznacznych (globów) nie są scalane; są po prostu połączone. (Wydaje mi się, że pamiętam, że strona podręcznika powłoki kiedyś to wyraźnie powiedziała, ale nie mogę jej teraz znaleźć).

Oczywiście, jeśli pliki mogą wzrosnąć c999.pdf, powinieneś użyć c?.pdf c??.pdf c???.pdf. Trzeba przyznać, że przy dużej liczbie cyfr może to być nudne. Możesz to trochę skrócić; na przykład, dla (do) pięciu cyfr, możesz użyć c?{,?{,?{,?{,?}}}}.pdf. Jeśli twoja lista nazw plików jest rzadka (np. Istnieje a c0.pdfi a c12345.pdf, ale niekoniecznie każda liczba pomiędzy), prawdopodobnie powinieneś ustawić tę nullglobopcję. W przeciwnym razie, jeśli (na przykład) nie masz plików z dwucyfrowymi liczbami, otrzymasz dosłowny c??.pdfargument do swojego programu.

Jeśli masz wiele prefiksów (na przykład , i , o numerach od jednego lub dwóch cyfr), można użyć oczywiste brute force podejście:a<number>.pdfb<number>.pdf c<number>.pdf

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

lub zwinąć do {a,b,c}?{,?}.pdf.

G-Man mówi „Przywróć Monikę”
źródło
1
Jest to najlepsze rozwiązanie, ponieważ jest poza wszelkimi roszczeniami szkicowy użytkowania ls, statlub cokolwiek innego; a także działa w bash zgodnie z żądaniem.
Kyle
5

Jeśli nie ma żadnych luk , poniższe informacje mogą okazać się pomocne (choć szkicowe i mało solidne w odniesieniu do przypadków skrajnych i ogólności) - tylko w celu uzyskania pomysłu:

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

Jeśli mogą występować luki, [ -f c${i}.pdf ]można dodać trochę czeków.

Edytuj również zobaczyć tę odpowiedź , zgodnie z którą możesz (używając Bash) użyć

gs [..args..] c{1..20}.pdf
sr_
źródło
Zasadniczo dobrym pomysłem jest cytowanie odniesień do zmiennych powłoki (np. "$FILES"I "$i"), chyba że masz dobry powód, aby tego nie robić i jesteś pewien, że wiesz, co robisz. (Podczas gdy nawiasy klamrowe mogą być ważne, nie są tak ważne jak cudzysłowy, więc na przykład "c$i.pdf"są wystarczająco dobre.) Komenda typu , gdzie zawiera listę plików oddzieloną spacjami, może wydawać się dobrym powodem do użyj bez cytowania (ponieważ nie będzie działać w tym kontekście). … (Ciąg dalszy)gs  [ …args… ]  $FILES$FILES$FILES"$FILES"
G-Man mówi „Przywróć Monikę”
(Ciąg dalszy) ... Ale zobaczyć konsekwencje Bezpieczeństwo zapominając zacytować zmiennej w bash / muszli POSIX , w szczególności moją odpowiedź na to , notatek dotyczących sposobu obsługi zmiennych multi-słowo jako tablice w bash (na przykład FILES=("c0.pdf")i FILES+=("c$i.pdf")); także ta odpowiedź , która wykorzystuje technikę, którą sugeruję.
G-Man mówi „Przywróć Monikę”
1

Cytuję i naprawiam odpowiedź Thora ... NIGDY nie analizuj!

Możesz użyć sort -V(rozszerzenie inne niż POSIX do sortowania):

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

(dla niektórych poleceń, najwyraźniej dla gs jest takim poleceniem, potrzebujesz „./ ” zamiast „ ” ... jeśli jedno nie działa, spróbuj drugiego)

Piotr
źródło
1
Dane wyjściowe Nie analizuj ls są wyświetlane, ponieważ ls wyświetla nazwy plików oddzielone nową linią, podczas gdy nowa linia jest tak samo ważna jak każda inna w nazwie pliku, ale tutaj robisz to samo, statale dodajesz kilka innych problemów (takich jak problemy z uruchamianiem nazw plików z -, problem, jeśli istnieje zbyt wiele plików, statbędąc non-przenośny komenda). A ponieważ używałeś operatora split + glob bez dostosowywania IFS lub wyłączania globów, nadal będziesz mieć problemy z nazwami plików ze spacją, tabulatorami lub znakami wieloznacznymi.
Stéphane Chazelas,
Aby użyć GNU sort -Vniezawodnie, że trzeba ${(z)"$(printf '%s\0' * | sort -zV)"}w zsh(chociaż zshma (n)do sortowania już numerycznej) lub readarray -td '' files < <(printf '%s\0' * | sort -zV)w bash4.4+.
Stéphane Chazelas,
@ StéphaneChazelas dzięki, i masz rację, że nowa linia może być problemem, ale to nie jedyny powód, aby nie analizować ls. I tak, byłem leniwy i nie dodałem - albo. Ale powinienem był użyć printf ... Zmienię to.
Peter
dla lssamego (czyli bez -l), jakie są te inne obawy ? Zauważ, że --to nie pomogłoby dla pliku o nazwie -.
Stéphane Chazelas,
@ StéphaneChazelas istnieją inne różnice między wersjami ... na przykład niektóre napisy „total 0”, a najnowsze wersje ls nawet przyklejają cytaty wokół rzeczy, których nie chcesz ... touch \"test\"; ls -1na przykład pokazuje '"test"'na moim ls. Po prostu nie jest przeznaczony do analizowania ... to interfejs użytkownika, a nie polecenie skryptowe.
Peter