【问题标题】:extracting location specific record using sed based on given criteria using bash script使用 bash 脚本根据给定标准使用 sed 提取特定于位置的记录
【发布时间】:2018-02-15 18:50:54
【问题描述】:

我想从一个 ASCII 文件中提取数据,该文件看起来像此处提供的以 1NAME 开头的块。以 1NAME 开头的块可以重复任意次数 - 我的文件只有一个块,有些文件多达 744 个:

AVERAGE   MODELNAME -- RUNNAME
 0  1  11121    0. 11122   24.
       -9700000         4000000   0   -241200000000   -1620000
1.00000      1000.00000  10 10   1   2   0    15.    11.     0.
    1    1  500  400
NAME
          11121      0.00     11121      1.00
   1NAME
 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00
 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00
 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00 0.0000000E+00
NAME
          11121      1.00     11121      2.00
   1NAME
 1.0000000E+00 45.0000000E+00 01.0000000E+00 115.0000000E+00 5.0000000E+00
 2.0000000E+00 66.0000000E+00 09.0000000E+00 180.0000000E+00 4.0000000E+00
 3.0000000E+00 80.0000000E+00 70.0000000E+00 130.0000000E+00 5.0000000E+00

我想从 (1) 文件中给定的重复位置中提取值,从“1NAME”之后开始,(2) 通过管道输出到文本文件并创建标头来标识它是从哪个位置提取的,以及 (3)创建一个自定义代码,该代码可以在 1NAME 之后接收多个位置(例如记录 1、5、8)的输入,并将它们输出到单独的输出中(例如:位置 1 的所有记录的一个输出,位置 5 的一个输出文件,.. .)。

例如,我想在给定的输入文件中获取 1NAME 之后的记录 1、5 和 8。每条记录的输出应在标记为 GRID#.txt 的单独记录特定文本文件中按如下方式输出:

GRID 1    
0.0000000E+00
00.0000000E+00
GRID 5
0.0000000E+00
5.0000000E+00
GRID 8
0.0000000E+00
09.0000000E+00

我能够使用 sed 一次提取数据。但是我需要从输入文件的多个位置提取数据。所以我试图把所有的信息放在一个脚本中。以下是我采取的步骤。

  1. 输入文件有多个空格和不一致的空行。所以我用 sed 去掉了多个空格,换成了一个空格。然后使用此步骤的管道输出,删除所有空行。这导致文件中的所有数据按每行一个值排列。

    sed 's/\s\+/\n/g' <input.txt>| sed '/^$/d
    
  2. 为了提取数据,然后我从步骤 1 的管道输出中使用了 sed 命令(格式如下)。

    sed -n -e 11p -e 50p
    
  3. 我尝试将所有这些命令作为带有自定义行号的 bash(或 csh,任一选项)脚本。我尝试(天真地)使用 foreach,然后了解到它不能在 bash 中使用。我将改用其他用户推荐的脚本。

    #!/bin/bash 
    set FILE=$cwd/sample_or_2day
    foreach GRID (23729)
    foreach GRIDTIME(28 41)
    sed 's/\s\+/\n/g' $FILE | sed '/^$/d' | sed '1,36d' > temp_out
    sed -n -e "$GRIDTIME" temp_out | tee $cwd/out_$GRID
    

感谢您的耐心等待。我是一个紧张的程序员,正在努力掌握基础知识。我花时间查看 sed 指令页面和用户支持论坛。欢迎任何建议 - 特别是有明确的说明。谢谢!

【问题讨论】:

  • 给定上面的输入,你想要哪个输出?
  • 我已经更新了#3下的输入输出文件格式和格式化代码
  • foreachbash 中不存在。它是for,而不是foreach,它支持两种不同的语法,这两种语法都与您使用的完全不同。 help for 将向您展示两者。
  • 注意,foreach 可以与 csh 一起使用吗?我无法很好地解释帮助页面。
  • 如果您询问的是csh 而不是bash(一种完全不同的语言),请相应地标记您的问题。您使用的 for 循环语法确实来自 csh,但它缺少 end 语句来关闭重复块。在 21 世纪没有充分的理由使用csh

标签: linux bash sed


【解决方案1】:

您尝试使用csh 脚本,但将您的问题标记为bash。我正在回答 bash 脚本。

您问题的核心是如何从格式化的打印输出中提取信息。一般来说,应该避免这种情况:应该使用知道被操纵的数据结构的编程环境,以避免在每一步重新解析。然而,在现实世界中,这种情况经常出现,人们必须应对它们。

您将所有空格转换为换行符的方法适用于您的情况。而不是多个sed 命令,实现它的最快方法是通过

tr -s ' ' '\n'

-s 选项将多次出现的目标字符压缩为一个,消除空行)

然后,您对每次出现包含1NAME 的行之后的第 7 行和第 14 行感兴趣。这是在sed 中由

完成的
sed -n -e '/^1NAME$/{n;n;n;n;n;n;n;p;n;n;n;n;n;n;n;p}'

这意味着:当你看到 1NAME 时,执行 nextline 命令七次,然后执行 print 命令。这样做两次。

您可以使用 shell 变量:

next7='n;n;n;n;n;n;n;p'

cat ./sample_or_2day | tr -s ' ' '\n' | sed -n -e '/^1NAME$/'"{$next7;$next7}"

会产生

0.0000000E+00
0.0000000E+00
66.0000000E+00
130.0000000E+00

正确,第一个块也被占用了。要跳过它,让我们添加您已经弄清楚的sed指令-e1,36d

$ cat ./sample_or_2day | tr -s ' ' '\n' | sed -n -e1,36d -e'/^1NAME$/'"{$next7;$next7}"
66.0000000E+00
130.0000000E+00

您可能还希望bash 为您构建sed 命令行:例如,命令

sed -n -e{7..29..7}p

会被 shell 扩展为

sed -n -e7p -e14p -e21p -e28p

如您所知,这意味着sed 将仅打印那些输入行。 您可能还想了解bash 中的for 循环,它们有两种不同的风格,例如:

for var in word1 word2 word3 ...; do ... ; done
for (( i=0; i<10; i++ )); do ...; done

现在,我不清楚您想如何管理输出文件。我提供了您的脚本的 bash 版本(提供 GRID 的值列表,而不仅仅是一个),它显示了 bash 中另一个可能的大括号扩展。

#!/bin/bash
FILE=./sample_or_2day
for GRID in 23729 23755 23768; do
  cat "$FILE" | tr -s ' ' '\n' | sed -n -e{28,41}p >> "./out_$GRID"
done

【讨论】:

  • 达里奥,非常感谢 - 这更清楚了。不幸的是,我不能对输出的格式做太多事情,因为它来自复杂的环境模型。我希望输出文件是 txt 或 csv 文件,以后可以在 Excel 中操作。此外,正如您所指出的,GRID 可以采用多个值。我只添加了一个来首先弄清楚代码的基本形式。如果我有多个 GRID 输入,如何包含它?
  • @learning_to_code 已编辑以显示如何为 GRID 提供多个输入。如果您对它感到满意,您应该接受答案。
  • 我在测试脚本时意识到,它并没有完全按照我的想法做 - 我错误地表达了我关于 GRID 的多个输入的问题。我想要的是对于 GRID 的每个输入,记录值都是唯一的。以 GRID 23729 为例,在 bash 脚本中,我们在 1NH3 之后的第 7 行选择记录。然后说下一个 GRID 23755,我们想在 1NH3 之后选择第 100 行。并将 GRID(s) 23729 和 23755 的输出放在单独的文件中。如何在 bash 脚本中应用它?
  • 请编辑明确指定此要求的问题,并指定允许根据 GRID 值计算行号的规则。如果它只是一个有两列(GRID,line_to_extract)的表存储在某个地方并且无法计算,那很好,但必须在问题中指定。当前版本已经将不同 GRID 的输出发送到单独的文件。
  • 编辑了问题。
【解决方案2】:

到目前为止,这对我有用,但不是 bash 脚本:

sed 's/\s\+/\n/g' ./sample_or_2day | sed '/^$/d' | sed '1,36d'| sed -n -e{23724..194842..97421}p > './out'

在上面的脚本中:

  • sed 's/\s+/\n/g' -> 用一个空格替换多个空格
  • sed '/^$/d' -> 从管道输出中删除空白行
  • sed '1,36d' -> 从管道输出中删除第 1-36 行
  • sed -n -e{23724..194842..97421}p -> 打印记录起始行 23724 和 97421 的间隔,直到第 194842 行
  • './out' -> 输出到标记为 out 的文件

【讨论】:

    【解决方案3】:

    如果您愿意添加 python 依赖项,您可能会发现这会有所帮助: http://stromberg.dnsalias.org/~strombrg/context-split.html

    或者尝试 awk,将 /^BEGIN/ 和 /^END/ 替换为您自己的正则表达式:

    #!/bin/sh
    awk '
    BEGIN { show=0 }
    /^END/ { show=0 }
    { if (show==1) print $0 }
    /^BEGIN/ { show=1 }' $@
    

    【讨论】:

    • 嗨 dstromberg:您能更明确地说明一下我的案例使用 awk 吗?目前尚不清楚如何将 show=0 部分替换为适用于此处的表达式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-20
    相关资源
    最近更新 更多