以相反的顺序打印文件行上的所有单词

Ber*_*ekz 11 bash array text-processing

我不知道如何让我的代码适用于更多行。

这是原始文件 t.txt:

Hello Earth
Hello Mars
Run Code Online (Sandbox Code Playgroud)

但我得到以下输出:

Mars Hello Earth Hello
Run Code Online (Sandbox Code Playgroud)

我的预期输出是这样的:

Earth Hello
Mars Hello
Run Code Online (Sandbox Code Playgroud)

一般来说,我想保持行序不变,但要颠倒单词。对于一般情况输入将是这样的:

one two 
four five
Run Code Online (Sandbox Code Playgroud)

和预期的输出是这样的:

two one
five four
Run Code Online (Sandbox Code Playgroud)

我的代码如下:

#!/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
Run Code Online (Sandbox Code Playgroud)

Ser*_*nyy 19

下面提供的所有示例都适用于行上有任意数量单词的一般情况。基本思想在任何地方都是一样的——我们必须逐行读取文件并反向打印单词。AWK 最好地促进了这一点,因为它已经拥有以编程方式完成的文本处理的所有必要工具,并且是最便携的 - 它可以与任何 awk 衍生产品一起使用,并且大多数系统都拥有它。Python 也有很多很好的字符串处理实用程序,可以让我们完成这项工作。我会说,它是适用于更现代系统的工具。恕我直言,Bash 是最不理想的方法,因为它具有便携性、潜在危险以及需要完成的大量“诡计”。

AWK

$ awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}' input.txt                                                  
Earth Hello 
Mars Hello 
Run Code Online (Sandbox Code Playgroud)

它的工作方式相当简单:我们在行上的每个单词中向后循环,打印用空格分隔的单词 - 这是由printf "%s ",$i函数(用于打印格式化字符串)和 for 循环完成的。NF变量对应于字段数。默认字段分隔符假定为空格。我们首先i为单词数设置一个一次性变量,并在每次迭代时递减该变量。因此,如果一行中有 3 个单词,我们将打印字段 $3,然后是 $2 和 $1。最后一次传递后,变量 i 变为 0,条件i>=1变为假,循环终止。为了防止线条拼接在一起,我们插入一个换行符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
Run Code Online (Sandbox Code Playgroud)

这里的想法略有不同。<操作符告诉你当前的 shell 重定向input.txt到 python 的stdin流,我们逐行读取。在这里,我们使用列表理解来创建行列表 - 这就是[ ' '.join(line.split()[::-1]) for line in sys.stdin ]部件所做的。该部分' '.join(line.split()[::-1])取一行,将其拆分为单词列表,通过 反转列表[::-1],然后从中' '.join()创建一个以空格分隔的字符串。因此,我们有一个更大的字符串列表。最后,'\n'.join()制作一个更大的字符串,每个项目通过换行符连接。

简而言之,这种方法基本上是一种“打破并重建”的方法。

巴什

$ awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}' input.txt                                                  
Earth Hello 
Mars Hello 
Run Code Online (Sandbox Code Playgroud)

并进行测试运行:

$ ./reverse_words.sh                                                                                              
Earth Hello 
Mars Hello 
Run Code Online (Sandbox Code Playgroud)

Bash 本身没有强大的文本处理能力。这里发生的事情是我们通过以下方式逐行读取文件

while IFS= read -r line
do
   # some code
done < text.txt
Run Code Online (Sandbox Code Playgroud)

这是一种常用技术,广泛用于 shell 脚本以逐行读取命令或文本文件的输出。每行都存储到$line变量中。

在里面我们有

bash -c 'i=$#; while [ $i -gt 0 ];do printf "%s " ${!i}; i=$(($i-1)); done' sh $line
Run Code Online (Sandbox Code Playgroud)

这里我们使用bashwith-c标志来运行一组用单引号括起来的命令。当-c使用时,bash将开始分配命令行参数到变量开始$0。因为它$0传统上用于表示程序的名称,所以我sh首先使用虚拟变量。

$line由于称为分词的行为,未引用的内容将被分解为单独的项目。分词在 shell 脚本中通常是不可取的,你经常会听到人们说“总是引用你的变量,比如“$foo”。然而,在这种情况下,分词对于处理简单文本是可取的。如果您的文本包含类似的内容$var,则可能会破坏这种方法。为此,以及其他几个原因,我认为 python 和 awk 方法更好。

至于内码,也很简单:将未加引号的$line部分拆分成词,传递给内码进行处理。我们获取参数的数量$#,将其存储到丢弃变量中i,然后再次 - 使用称为变量间接的东西打印出每个项目 - 这就是${!i} 部分(请注意,这是 bashism - 它在其他 shell 中不可用)。再一次,我们使用printf "%s "以空格分隔的方式打印出每个单词。完成后,echo将附加一个换行符。

本质上,这种方法是 awk 和 python 的混合。我们逐行读取文件,但分而治之,使用 的几个bash功能来完成这项工作。

可以使用 GNUtac命令完成更简单的变化,并再次使用分词。tac用于反转输入流或文件的行,但在这种情况下,我们指定-s " "使用空格作为分隔符。因此,var将包含一个以相反顺序以换行符分隔的单词列表,但由于$var没有被引用,换行符将被替换为空格。诡计,再次不是最可靠的,但有效。

#!/bin/bash

while IFS= read -r line
do
     var=$(tac -s " " <<< "$line" )
     echo  $var
done < input.txt
Run Code Online (Sandbox Code Playgroud)

测试运行:

这是具有任意输入行的 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 
Run Code Online (Sandbox Code Playgroud)

额外: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
Run Code Online (Sandbox Code Playgroud)


hee*_*ayl 9

只需交换周围的话,与awk

awk '{print $2, $1}'
Run Code Online (Sandbox Code Playgroud)

例子:

% cat bar.txt
Hello Earth
Hello Mars

% awk '{print $2, $1}' bar.txt
Earth Hello
Mars Hello
Run Code Online (Sandbox Code Playgroud)