Все примеры, представленные ниже, подходят для общего случая, когда в строке есть произвольное количество слов. Основная идея везде одна и та же - мы должны читать файл построчно и печатать слова в обратном порядке. AWK облегчает это лучше всего, потому что в нем уже есть все необходимые инструменты для программной обработки текста, и он наиболее переносим - его можно использовать с любой производной от awk, и в большинстве систем они есть. В Python также есть несколько хороших утилит для обработки строк, которые позволяют нам выполнять эту работу. Я бы сказал, что это инструмент для более современных систем. Bash, IMHO, является наименее желательным подходом из-за переносимости, потенциальных опасностей и количества "уловок", которые необходимо сделать.
$ awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}' input.txt
Earth Hello
Mars Hello
Это работает довольно просто: мы перебираем в обратном направлении каждое слово в строке, печатаем слова, разделенные пробелом - это делается с помощью printf "% s", $ i
функция (для печати форматированных строк) и for-loop. Переменная NF
соответствует количеству полей. Предполагается, что разделителем полей по умолчанию является пробел. Мы начинаем с установки переменной выброса i
на количество слов и на каждой итерации уменьшаем значение переменной. Таким образом, если в строке 3 слова, мы печатаем поле $ 3, затем $ 2 и $ 1. После последнего прохода переменная i становится равной 0, условие i> = 1
становится ложным, и цикл завершается. Чтобы строки не сращивались, мы вставляем новую строку, используя print ""
.Блоки кода AWK {}
обрабатываются для каждой строки в этом случае (если есть условие соответствия перед блоком кода, это зависит от совпадения для блока кода, который должен быть выполнен или нет).
Для тех, кто любит альтернативные решения, вот python:
$ python -c "import sys;print '\n'.join([ ' '.join(line.split()[::-1]) for line in sys.stdin ])" < input.txt
Earth Hello
Mars Hello
Идея здесь немного другая. Оператор <
указывает вашей текущей оболочке перенаправить input.txt
в поток Python stdin
, и мы читаем этот построчный. Здесь мы используем понимание списка для создания списка строк - это то, что делает часть ['' .join (line.split () [:: - 1]) для строки в sys.stdin]
. Часть '' .join (line.split () [:: - 1])
берет строку, разбивает ее на список слов, переворачивает список через [:: - 1]
, а затем '' .join ()
создает из него строку, разделенную пробелами. В результате у нас есть список более крупных строк. Наконец, '\ n'.join ()
создает строку еще большего размера, при этом каждый элемент присоединяется через новую строку.
Короче говоря, этот метод представляет собой подход «разорвать и перестроить».
#!/bin/bash
while IFS= read -r line
do
bash -c 'i=$#; while [ $i -gt 0 ];do printf "%s " ${!i}; i=$(($i-1)); done' sh $line
echo
done < input.txt
И тестовый запуск:
$ ./reverse_words.sh
Earth Hello
Mars Hello
Сам Bash не обладает сильными возможностями обработки текста. Здесь происходит то, что мы читаем файл построчно через
while IFS= read -r line
do
# some code
done < text.txt
. Это частый метод и широко используется в сценариях оболочки для построчного чтения вывода команды или текстового файла. Каждая строка сохраняется в переменной $ line
.
Внутри у нас есть
bash -c 'i=$#; while [ $i -gt 0 ];do printf "%s " ${!i}; i=$(($i-1)); done' sh $line
Здесь мы используем bash
с флагом -c
для выполнения набора команд, заключенных в одинарные кавычки.Когда используется -c
, bash
начнет назначать аргументы командной строки переменным, начиная с $ 0
. Поскольку этот $ 0
традиционно используется для обозначения имени программы, я сначала использую фиктивную переменную sh
.
Некотируемая строка $
будет разбита на отдельные элементы из-за поведения, известного как разделение слов. Разделение слов часто нежелательно при написании сценариев оболочки, и вы часто слышите, как люди говорят: «Всегда указывайте переменные в кавычках, например,« $ foo »». Однако в этом случае для обработки простого текста желательно разделение слов. Если ваш текст содержит что-то вроде $ var
, это может нарушить этот подход. По этой и нескольким другим причинам я бы сказал, что подходы python и awk лучше.
Что касается внутреннего кода, он также прост: некотируемая $ строка
разбивается на слова и передается внутреннему коду для обработки. Мы берем количество аргументов $ #
, сохраняем его в переменной выброса i
и снова распечатываем каждый элемент, используя нечто, известное как косвенное обращение к переменной - это $ {! i}
часть (обратите внимание, что это bashism - она недоступна в других оболочках). И снова мы используем printf "% s"
для вывода каждого слова через пробел. Как только это будет сделано, echo
добавит новую строку.
По сути, этот подход представляет собой смесь awk и python. Мы читаем файл построчно, но разделяем и захватываем каждую строку, используя для этого несколько функций bash
.
Более простой вариант можно сделать с помощью команды GNU tac
и снова поиграть с разбиением слов. tac
используется для переворота строк входного потока или файла,но в этом случае мы указываем -s ""
, чтобы использовать пробел в качестве разделителя. Таким образом, var
будет содержать список слов, разделенных новой строкой, в обратном порядке, но из-за того, что $ var
не цитируется, новая строка будет заменена пробелом. Уловка, и опять же не самый надежный, но работает.
#!/bin/bash
while IFS= read -r line
do
var=$(tac -s " " <<< "$line" )
echo $var
done < input.txt
А вот 3 метода с произвольными строками ввода
$ cat input.txt
Hello Earth end of line
Hello Mars another end of line
abra cadabra magic
$ ./reverse_words.sh
line of end Earth Hello
line of end another Mars Hello
magic cadabra abra
$ python -c "import sys;print '\n'.join([ ' '.join(line.split()[::-1]) for line in sys.stdin ])" < input.txt
line of end Earth Hello
line of end another Mars Hello
magic cadabra abra
$ awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}' input.txt
line of end Earth Hello
line of end another Mars Hello
magic cadabra abra
Та же идея, что и с python - мы разбиваем каждую строку на массив слов, меняем массив и Распечатай.
$ perl -lane '@r=reverse(@F); print "@r"' input.txt
line of end Earth Hello
line of end another Mars Hello
magic cadabra abra
$ ruby -ne 'puts $_.chomp.split().reverse.join(" ")' < input.txt
line of end Earth Hello
line of end another Mars Hello
magic cadabra abra
sed
Следующий GNU sed
программа использует цикл для перемещения каждого слова (начиная с первого) в конец строки. Более подробная информация вставлена в код в виде комментариев.
sed -r '
# Mark the current end of the line by appending a LF character ("\n")
G
# Main loop: move the first word of the line just after the LF
# and repeat until the LF is at the beginning of the line
:loop
s/([^[:space:]]+)(.*\n)/\2\1 /
t loop
# Remove remaining spaces up to the LF and the superfluous trailing space
s/.*\n| $//g
'
Версия только для записи:
sed -r 'G; :loop; s/(\S+)(.*\n)/\2\1 /; t loop; s/.*\n| $//g'
Тест:
$ sed -r '...' <<< "The quick
brown fox jumps
over
the lazy dog"
... дает:
quick The
jumps fox brown
over
dog lazy the
Переносимость (POSIXly):
sed '
G
:loop
s/\([^[:space:]]\{1,\}\)\(.*\n\)/\2\1 /
t loop
s/ $//
s/.*\n//'
Просто поменяйте местами слова с помощью awk
:
awk '{print $2, $1}'
Пример:
% cat bar.txt
Hello Earth
Hello Mars
% awk '{print $2, $1}' bar.txt
Earth Hello
Mars Hello
Есть rev
для символов и tac
для строк, но ничего (я не знаю о )для слов. Это самая простая идиома Bash, которая мне подходит.
while read line; do echo $(echo $line | tr " " "\n" | tac); done < $1