【问题标题】:Reading text file, change order of columns of some lines读取文本文件,更改某些行的列顺序
【发布时间】:2018-04-23 05:52:44
【问题描述】:

我的输入文件格式为:

   0     1     0     0     0     1     1     0     0    0 / 1    0 / 1    0 / 1
   0     1     0 3/4     1     0     0 1/4     0     0    -1 1/2
   0    -1     0 1/4    -1     0     0 3/4     0     0     1 1/2

我想重新排列其中包含分数的行的顺序。目前我有:

#!bin/bash
filename="input.txt"
while ((i++)); read -r line; do
  re='[0-9][/][0-9]';
  if [[ $line =~ $re ]]
    then
      echo $line
  fi
done < "$filename"

这将回显第二行和第三行。是否有一个 awk 或 sed 命令我可以用来让这两行更改它们的顺序(保持第一行不变)为

$1,$2,$3,$5,$6,$7,$9,$10,$11,$4,$8,$12

这将使我的文件现在看起来像

   0     1     0     0     0     1     1     0     0    0 / 1    0 / 1    0 / 1
   0     1     0     1     0     0     0     0    -1 3/4 1/4 1/2
   0    -1     0    -1     0     0     0     0     1 1/4 3/4 1/2

【问题讨论】:

  • @anubhava 已编辑
  • 我知道,我只是想用一种更好的方式来描述我的问题,这可能只会让事情变得更糟。开始认为我不会弄清楚这一点,只需要手动更改所有行
  • 您的列是用哪个字符分隔的?这些行是否包含前导分隔符?

标签: bash shell awk sed


【解决方案1】:

最好使用awk

awk -v OFS='\t' '/[0-9]\/[0-9]/{print $1,$2,$3,$5,$6,$7,$9,$10,$11,$4,$8,$12; next} 1' file

0     1     0     0     0     1     1     0     0    0 / 1    0 / 1    0 / 1
0   1   0   1   0   0   0   0   -1  3/4 1/4 1/2
0   -1  0   -1  0   0   0   0   1   1/4 3/4 1/2

【讨论】:

  • 它没有,分数保留在行内。不过找到了解决方案。
  • 它与问题中显示的预期输出不匹配吗?
【解决方案2】:

您可以使用 awk 轻松做到这一点,但我认为定义游戏规则很重要。 在以下假设下:

  • 分数是任何形式:a/ba / ba/ b
  • 如果分数出现在第 4 列或第 8 列中,请重新排列列。
  • 您希望保持格式正确

考虑到这一点,您可以使用以下 awk 代码

awk 'BEGIN{format="%4s%6s%6s%6s%6s%6s%6s%6s%6s%7s%7s%7s\n"}
     { gsub(/[[:blank:]]*\/[[:blank:]]*/,"/",$0); $0=$0 }
     ($4 ~ /\//) || ($8 ~ /\//) { 
        $12=$4" "$8" "$12
        $4=""; $8=""
        $0=$0
     }                                           
     { printf format,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12 }
    ' file.txt

执行以下操作:

  • 用单个/替换所有" / "或任何变体

  • $0 = $0 重新定义字段,即在您的前两行中,您 将从 18 个字段移至 12 个字段

  • 如果字段 4 或 8 中出现分数(即 /),则重新定义字段 12,删除字段 4 和 8 并再次执行 $0=$0

  • 以正确的格式打印。

注意:在上面的例子中,分数有不同的输出(没有空格)

以上将为您提供以下输出:

   0     1     0     0     0     1     1     0     0    0/1    0/1    0/1
   0     1     0     1     0     0     0     0    -1    3/4    1/4    1/2
   0    -1     0    -1     0     0     0     0     1    1/4    3/4    1/2

如果你不想在第一行改变你的分数,那么你可以像这样很容易地做到这一点

awk 'BEGIN{format="%4s%6s%6s%6s%6s%6s%6s%6s%6s%7s%7s%7s\n"}
     (NF>12) { print; next }
     { 
        $12=$4" "$8" "$12
        $4=""; $8=""
        $0=$0
        printf format,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12
     }
    ' file.txt

在这里你假设,

  • 如果一行有超过 12 个字段,只需打印它

  • 否则,洗牌

然而,这不太可靠,因为一切都取决于分数在第 4、8 和 12 列中的输入方式。 IE。它们必须输入时不带空格。输出将如下所示:

   0     1     0     0     0     1     1     0     0    0 / 1    0 / 1    0 / 1
   0     1     0     1     0     0     0     0    -1    3/4    1/4    1/2
   0    -1     0    -1     0     0     0     0     1    1/4    3/4    1/2

【讨论】:

  • @RobS。如果您有现代版本的 gawk,您可以进行就地替换(参考 here)。只需将awk 替换为gawk -i inplace。如果您的 awk 版本不支持 inplace,那么您可以使用awk '...' file.txt &gt; file.new.txt; mv file.new.txt file.txt。基本上是制作一个新文件并重命名它。
  • gawk 不起作用,但简单地将输出写入一个新文件并移动该文件。非常感谢您的帮助!
【解决方案3】:

@anubhava 是比我更好的解决方案。既然我写了其他代码,请注意。

#!/bin/bash

filename="input.txt"

awk '
{
for (i=1; i <= NF; i++)
  if ( $(i+1) == "/" || $i == "/" || $(i-1) == "/") {
    printf "MM%sMM",$i" "$(i+1)" "$(i+2)
    i = i+2
  } else if ( match ($i, /^[[:digit:]]\/[[:digit:]]/) ) {
    printf "MM%sMM",$i
  } else {
    printf "MM%sMM",$i
  }
  printf "\n"
}' $filename | sed -e 's/MMMM/MM/g;s/^MM//;s/MM/\t/g' 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多