强制sed解决方案

强制sed解决方案

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

这是原始文件 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

这里我们使用bashwith-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

相关内容