我不知道如何使我的代码适用于更多行。
这是原始文件 t.txt:
Hello Earth
Hello Mars
但我得到以下输出:
Mars Hello Earth Hello
我的预期输出是这样的:
Earth Hello
Mars Hello
一般来说,我想保持行序相同,但单词相反。对于一般情况输入将是这样的:
one two
four five
预期输出是这样的:
two one
five four
我的代码如下:
#!/bin/bash
text=$(cat $1)
arr=($text)
al=${#arr[@]}
let al="al-1"
while (($al >= 0))
do
echo -n "${arr[al]}"
echo -n " "
let al="al - 1"
done
echo
答案1
下面提供的所有示例都适用于线路上有任意数量的单词的一般情况。基本思想在任何地方都是相同的 - 我们必须逐行读取文件并反向打印单词。 AWK 最好地促进了这一点,因为它已经拥有以编程方式完成文本处理的所有必要工具,并且是最可移植的 - 它可以与任何 awk 衍生物一起使用,并且大多数系统都有它。 Python 还有很多很好的字符串处理实用程序,可以帮助我们完成这项工作。我想说,它是更现代系统的工具。恕我直言,由于可移植性、潜在危险以及需要完成的“欺骗”工作量,Bash 是最不理想的方法。
AWK
$ awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}' input.txt
Earth Hello
Mars Hello
它的工作方式相当简单:我们向后循环该行上的每个单词,打印用空格分隔的单词 - 这是通过printf "%s ",$i
函数(用于打印格式化字符串)和 for 循环完成的。NF
变量对应于字段的数量。默认字段分隔符假定为空格。我们首先将一个一次性变量设置i
为单词数,然后在每次迭代时减少该变量。因此,如果一行中有 3 个单词,我们将打印字段 $3,然后打印 $2 和 $1。最后一遍之后,变量 i 变为 0,条件i>=1
变为 false,循环终止。为了防止行被拼接在一起,我们使用 插入换行符print ""
。在这种情况下, AWK 代码块{}
是针对每一行进行处理的(如果代码块前面有匹配条件,则取决于是否匹配该代码块)。
Python
对于那些喜欢替代解决方案的人,这里是 python:
$ python -c "import sys;print '\n'.join([ ' '.join(line.split()[::-1]) for line in sys.stdin ])" < input.txt
Earth Hello
Mars Hello
这里的想法略有不同。<
运算符告诉当前的 shell 重定向input.txt
到 python 的stdin
流,我们逐行读取该内容。在这里,我们使用列表理解来创建行列表 - 这就是该[ ' '.join(line.split()[::-1]) for line in 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
这是一种常见的技术,广泛用于 shell 脚本编写中,以逐行读取命令或文本文件的输出。每行都存储到$line
变量中。
在里面我们有
bash -c 'i=$#; while [ $i -gt 0 ];do printf "%s " ${!i}; i=$(($i-1)); done' sh $line
这里我们使用bash
with-c
标志来运行一组用单引号括起来的命令。-c
使用时,bash
将开始将命令行参数分配给以$0
.开头的变量。因为它$0
传统上用于表示程序名称,所以我sh
首先使用虚拟变量。
由于称为分词的行为,未引用的内容$line
将被分解为单独的项目。在 shell 脚本编写中,分词通常是不可取的,并且您经常会听到人们说“始终引用变量,例如“$foo”。”然而,在这种情况下,分词对于处理简单文本来说是理想的。如果您的文本包含类似的内容$var
,则可能会破坏这种方法。出于这个原因以及其他几个原因,我认为 python 和 awk 方法更好。
至于内部代码,也很简单:将不带引号的部分$line
拆分成单词,然后传递给内部代码进行处理。我们获取参数的数量$#
,将其存储到扔掉的变量中i
,然后再次使用称为变量间接的东西打印出每个项目 - 这就是部分${!i}
(请注意,这是 bashism - 它在其他 shell 中不可用)。再次,我们用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
额外:perl 和 ruby
与 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
答案2
只需交换单词即可awk
:
awk '{print $2, $1}'
例子:
% cat bar.txt
Hello Earth
Hello Mars
% awk '{print $2, $1}' bar.txt
Earth Hello
Mars Hello
答案3
强制sed
解决方案
以下 GNUsed
程序使用循环来移动行末尾的每个单词(从第一个单词开始)。更多详细信息作为注释插入到代码中。
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//'
答案4
有rev
人物和tac
台词,但(据我所知)没有文字。这是对我来说最简单的 Bash 习惯用法。
while read line; do echo $(echo $line | tr " " "\n" | tac); done < $1