Czy można łączyć polecenia tr, aby uniknąć wielu procesów tr w potoku?


11

Mam kilka plików txt, chciałbym wypisać je małymi literami, tylko alfabetycznie i jedno słowo na wiersz, mogę to zrobić za pomocą kilku trpoleceń w potoku:

tr -d '[:punct:]' <doyle_sherlock_holmes.txt | tr '[:upper:]' '[:lower:]' | tr ' ' '\n'

Czy można to zrobić w jednym skanie? Mógłbym napisać program w C, aby to zrobić, ale czuję, istnieje sposób, aby zrobić to za pomocą tr, sed, awklub perl.


Z jakiego systemu operacyjnego korzystasz? Czy masz dostęp do narzędzi GNU?
terdon

Odpowiedzi:


9

Możesz łączyć wiele tłumaczeń (oprócz złożonych przypadków obejmujących nakładające się zestawy zależne od ustawień regionalnych), ale nie możesz łączyć usuwania z tłumaczeniem.

<doyle_sherlock_holmes.txt tr -d '[:punct:]' | tr '[:upper:] ' '[:lower:]\n'

Dwa połączenia trbędą prawdopodobnie szybsze niż pojedyncze połączenie z bardziej złożonymi narzędziami, ale jest to bardzo zależne od wielkości wejściowej, proporcji różnych znaków, implementacji tri konkurujących narzędzi, systemu operacyjnego, liczby rdzeni itp.


Nie jestem pewien, czy łączętr -s '[:upper:] [:punct:]' '[:lower:]\n' <doyle_sherlock_holmes.txt
Costas

1
@Costas To przekształciłoby interpunkcję w znaki nowej linii. Może być OK dla tej konkretnej aplikacji, ale wynik nie jest taki sam jak oryginał.
Gilles „SO- przestań być zły”

@Casas - chociaż nowa linia może być tutaj dostępna, nie sądzę, że byłoby ściskanie wielkich liter. Na przykład: printf 'A.AAAA,A' | tr -s '[:upper:] [:punct:]' '[:lower:][\n*]'dostaje a\na\na', a transformacja dla ... '[:lower:]\n'może niekoniecznie w ogóle nic zrobić '[:punct:]'- niektóre trs obetną set1, aby dopasować 2, a niektóre wykonają domniemane [\n*]. Lepiej po prostu użyć tam zasięgu.
mikeserv

4

Oto kilka podejść:

  • GNU grepi tr: znajdź wszystkie słowa i umieść je małymi literami

    grep -Po '\w+' file | tr '[A-Z]' '[a-z]'
  • GNU grep i perl: jak wyżej, ale perl obsługuje konwersję na małe litery

    grep -Po '\w+' file | perl -lne 'print lc()'
  • perl: znajdź wszystkie znaki alfabetyczne i wydrukuj je małymi literami (dzięki @steeldriver):

    perl -lne 'print lc for /[a-z]+/ig' file
  • sed: usuwa wszystkie znaki, które nie są alfabetyczne ani spacje, zastępuje wszystkie znaki alfabetyczne ich małymi wersjami i zastępuje wszystkie spacje nowymi liniami. Zauważ, że zakłada to, że wszystkie białe znaki to spacje, bez tabulatorów.

    sed 's/[^a-zA-Z ]\+//g;s/[a-zA-Z]\+/\L&/g; s/ \+/\n/g' file

2
Czy coś podobnego perl -lne 'print lc for /[[:alpha:]]+/g'też zadziała? czy jest to kiepski styl? (Jestem nowy w Perlu i próbuję się uczyć!)
steeldriver

@steeldriver tak, byłoby miło! Jeśli uczysz się Perla, jestem pewien, że natrafiłeś na jego motto: TMTOWTDI :) Dzięki, dodam to.
terdon

3
Dzięki nowej wersji (> 4.2.1)sed -z 's/\W*\(\w\+\)\W*/\L\1\n/g'
Costas

@Costas ah, sedmożna \wteraz zrobić ? Fajne!
terdon

@terdon - robi się tak przez jakiś czas, ale ponieważ Costas o tym nie wspomniał, myślę, że najciekawszą rzeczą w powyższym komentarzu jest przełącznik ero delimit GNU sed- -zzmienia on cyklicznie na \0NULs, a nie na nowe linie. Całkiem fajne, gdy robisz coś takiego tar -c . | tr -s \\0 | sed -z ...- ale trochę powolne.
mikeserv

4

Tak. Możesz to zrobić w / trw ustawieniach ASCII (co w przypadku GNU jest trswego rodzaju jedyną funkcją) . Możesz użyć klas POSIX lub możesz odwoływać się do wartości bajtów każdego znaku według liczby ósemkowej. Możesz także podzielić ich transformacje na zakresy.

LC_ALL=C tr '[:upper:]\0-\101\133-140\173-\377' '[:lower:][\n*]' <input

Powyższe polecenie przekształciłoby wszystkie wielkie litery na małe, całkowicie zignorowało wielkie litery i przekształciło wszystkie inne znaki na nowe znaki. Oczywiście wtedy kończysz z mnóstwem pustych linii. Do tr -spowtórzenia queeze przełączania mogą być przydatne w tej sprawie, ale jeśli używasz go u boku [:upper:]do [:lower:]transformacji wtedy można skończyć ściskając duże litery, jak również. W ten sposób nadal wymaga drugiego filtra, takiego jak ...

LC... tr ... | tr -s \\n

...lub...

LC... tr ... | grep .

... a więc jest o wiele mniej wygodne niż robienie ...

LC_ALL=C tr -sc '[:alpha:]' \\n <input | tr '[:upper:]' '[:lower:]'

... który ściska -cdopełnienie znaków alfabetycznych sekwencją w jeden nowy wiersz, a następnie przekształca górne na dolne po drugiej stronie rury.

Nie oznacza to, że zakresy tego rodzaju nie są przydatne. Rzeczy jak:

tr '\0-\377' '[1*25][2*25][3*25][4*25][5*25][6*25][7*25][8*25][9*25][0*]' </dev/random

... może być bardzo przydatny, ponieważ konwertuje bajty wejściowe na wszystkie cyfry w rozproszonym spektrum ich wartości. Nie marnuj, nie chcesz, wiesz.

Może to obejmować inny sposób przeprowadzenia transformacji dd.

tr '\0-\377' '[A*64][B*64][C*64][D*64]' </dev/urandom |
dd bs=32 cbs=8 conv=unblock,lcase count=1

dadbbdbd
ddaaddab
ddbadbaa
bdbdcadd

Ponieważ ddmoże to zrobić zarówno unblocki lcasekonwersji w tym samym czasie, może to być nawet możliwe, aby przejść dużo pracy poza nim. Ale to może być naprawdę użyteczne tylko wtedy, gdy możesz dokładnie przewidzieć liczbę bajtów na słowo - lub przynajmniej można wcześniej wstawić każde słowo spacjami do przewidywalnej liczby bajtów, ponieważ unblockje końcowe spacje na końcu każdego bloku.


+2 punkty bonusowe za ddzaangażowanie :)
tlehman

@TobiLehman - Bardzo się cieszę, że zatwierdzasz.
mikeserv
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.