Чи можуть бути команди tr ланцюгами, щоб уникнути декількох процесів tr у трубопроводі?


11

У мене є маса файлів txt, я хотів би виводити їх у нижньому регістрі, лише алфавітом та одним словом на рядок, я можу це зробити за допомогою декількох trкоманд у конвеєрі, як це:

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

Чи можливо це зробити за одне сканування? Я міг би написати програму C , щоб зробити це, але я відчуваю, що є спосіб зробити це з допомогою tr, sed, awkабо perl.


Яку ОС ви використовуєте? Чи маєте ви доступ до інструментів GNU?
terdon

Відповіді:


9

Ви можете комбінувати кілька перекладів (за винятком складних випадків, пов’язаних із перекриттями наборів, залежних від локалі), але ви не можете поєднати видалення з перекладом.

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

Два виклики tr, швидше за все, будуть швидшими, ніж один виклик більш складним інструментам, але це дуже залежить від розміру вводу, пропорцій різних символів, реалізації trта конкуруючих інструментів, операційної системи, кількості ядер тощо.


Я не впевнений, що поєднуютьсяtr -s '[:upper:] [:punct:]' '[:lower:]\n' <doyle_sherlock_holmes.txt
Костас

1
@Costas Це перетворило б розділові знаки на нові рядки. Це може бути нормально для цієї програми, але вихід не такий, як оригінал.
Жил 'ТАК - перестань бути злим'

@Costas - хоча річ з нового рядка тут може бути помітна, я не думаю, що стискання великих літер не буде. Наприклад: printf 'A.AAAA,A' | tr -s '[:upper:] [:punct:]' '[:lower:][\n*]'отримує a\na\na', і перетворення для ... '[:lower:]\n'не може взагалі нічого робити '[:punct:]'взагалі - деякі trs будуть усічувати set1 у відповідність до 2, а деякі - мається на увазі [\n*]. Краще просто використовувати там асортимент.
mikeserv

4

Ось кілька підходів:

  • GNU grepі tr: знайдіть усі слова та зробіть їх малими літерами

    grep -Po '\w+' file | tr '[A-Z]' '[a-z]'
  • GNU grep and perl: як вище, але perl обробляє перетворення у малі регістри

    grep -Po '\w+' file | perl -lne 'print lc()'
  • perl: знайдіть усі алфавітні символи та роздрукуйте їх у малому регістрі (спасибі @steeldriver):

    perl -lne 'print lc for /[a-z]+/ig' file
  • sed: видаліть усі символи, які не є алфавітними або пробілами, замініть усі алфавітні символи їх малими версіями та замініть усі пробіли новими рядками. Зауважте, що це передбачає, що весь пробіл - це пробіли, а не вкладки.

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

2
Чи щось на кшталт perl -lne 'print lc for /[[:alpha:]]+/g'теж працює? чи це поганий стиль? (Я новачок у
перлі

@steeldriver так, приємно! Якщо ви вивчаєте Perl, я впевнений, що ви натрапили на його девіз: TMTOWTDI :) Дякую, я додам його.
terdon

3
З новою версією (> 4.2.1)sed -z 's/\W*\(\w\+\)\W*/\L\1\n/g'
Костас

@Costas ах, тепер sedможна зробити \w? Класно!
тердон

@terdon - це зроблено , що на деякий час, але, так як Костас не згадував про це, я думаю , що найцікавіше вище коментаря є GNU sed«s -zеро розмежувати перемикач - це цикли через \0NULS , а не переклади рядків. Дуже круто, коли робиш щось на кшталт tar -c . | tr -s \\0 | sed -z ...- але начебто повільно.
mikeserv

4

Так. Це можна зробити trв локальній системі ASCII (що для GNU так trчи інакше є єдиним напрямком роботи) . Ви можете використовувати класи POSIX або посилатись на значення байтів кожного символу за восьмеричним числом. Ви також можете розділити їх перетворення на діапазони.

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

Наведена вище команда перетворить усі великі символи в малі, повністю ігнорує рядкові символи та перетворить усі інші символи в нові рядки. Звичайно, тоді ви намотуєте тону пустих ліній. У tr -squeeze повтори перемикач може бути корисним в тому випадку, але якщо ви використовуєте його поряд з [:upper:]до [:lower:]трансформації , то ви завершуєте видавлювання прописні символи. Таким чином, він все ще потребує другого фільтра, як ...

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

... або ...

LC... tr ... | grep .

... і тому воно стає набагато менш зручним, ніж робити ...

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

... який стискає -cвтілення алфавітних символів за послідовністю в один шматок нового рядка, потім перетворює верхній на нижній з іншого боку труби.

Це не означає, що діапазони такого характеру не є корисними. Такі речі:

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

... може бути дуже зручним, оскільки він перетворює вхідні байти на всі цифри за розширеним спектром їх значень. Не витрачайте, не хочете, знаєте.

Ще один спосіб зробити перетворення міг би включати 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

Оскільки ddможна робити unblockі lcaseперетворення, і перетворення одночасно, можливо, велику частину роботи можна передати йому. Але це може бути дійсно корисним лише в тому випадку, якщо ви зможете точно передбачити кількість байтів на кожне слово - або, принаймні, зможете заздалегідь unblockпрокладати кожне слово з пробілами до передбачуваного числа байтів, тому що з'їдає пробіли в кінці кожного блоку.


+2 бонусні бали за ddзалучення :)
tlehman

@TobiLehman - Я дуже радий, що ти схвалюєш.
mikeserv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.