Можно ли связать команды tr, чтобы избежать нескольких процессов tr в конвейере?

11

У меня есть куча TXT-файлов, я хотел бы вывести их в нижнем регистре, только в алфавитном порядке и по одному слову в строке, я могу сделать это с помощью нескольких trкоманд в конвейере, например:

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

Возможно ли сделать это за один просмотр? Я мог бы написать программу C , чтобы сделать это, но я чувствую, что есть способ сделать это с помощью tr, sed, awkили perl.

tlehman
источник
Какую ОС вы используете? У вас есть доступ к инструментам 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', и преобразование for ... '[:lower:]\n'может вообще не делать вообще ничего '[:punct:]'- некоторые trусекают set1 до совпадения 2, а другие подразумевают [\n*]. Лучше просто использовать диапазон там.
mikeserv
4

Вот несколько подходов:

  • GNU grepи tr: найти все слова и сделать их строчными

    grep -Po '\w+' file | tr '[A-Z]' '[a-z]'
  • GNU grep и 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'также работать? или это плохой стиль? (Я новичок в Perl и пытаюсь учиться!)
Steeldriver
@steeldriver да, хорошо бы! Если вы изучаете Perl, я уверен, что вы столкнулись с его девизом: TMTOWTDI :) Спасибо, я добавлю его.
Тердон
3
В новой версии (> 4.2.1)sed -z 's/\W*\(\w\+\)\W*/\L\1\n/g'
Костас
@ Костас ах, sedможешь сделать \wсейчас? Прохладно!
Тердон
@terdon - это сделано , что на некоторое время, но, так как Костас не упоминал об этом, я думаю , что самое интересное выше комментария является GNU sed«s -zэро разграничить переключатель - это циклы через \0NULS , а не переводы строк. Довольно круто, когда ты делаешь что-то вроде tar -c . | tr -s \\0 | sed -z ...- но довольно медленно.
mikeserv
4

Да. Вы можете сделать это с trпомощью локали ASCII (которая в trлюбом случае является для GNU своего рода единственной задачей) . Вы можете использовать классы POSIX или ссылаться на байтовые значения каждого символа по восьмеричному числу. Вы также можете разделить их преобразования по диапазонам.

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

Приведенная выше команда преобразует все заглавные буквы в строчные, полностью игнорирует строчные буквы и преобразует все остальные символы в символы новой строки. Конечно, тогда вы получите тонны пустых строк. Переключатель tr -squeeze repeat может быть полезен в этом случае, но если вы используете его вместе [:upper:]с [:lower:]преобразованием to, то вы также сжимаете заглавные буквы. Таким образом, он все еще требует второго фильтра, как ...

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съедает конечные пробелы в конце каждого блока.

mikeserv
источник
+2 бонусных балла за ddучастие :)
tlehman
@TobiLehman - Я очень рад, что вы одобряете.
mikeserv