【问题标题】:Parsing a file with bash script使用 bash 脚本解析文件
【发布时间】:2020-02-09 22:04:12
【问题描述】:

我有一个包含多行的文件,其结构如下所示

MSH|^~\&|Xatidok|V10.0.2.000|OSestra|x-tention|201203060855||ADT^A03|2914|P|2.3^AA&BB
EVN|A03|201203060855|201203060855|01|Fidani
PID|||00019380|2012049008^120005548^302830|PATIDOK-person^InRid^|Rudi|19111111|F|||Rose |A|Pens.
NK1||IRergrun^RROSlf^||Rose ^^Wels^^4600^A|07242123123|||||||||||||||||||||||||||||||
PV1||I|1212^G442^G442-||0|||||||||||2012049008|General|||||||||||||||||||12|||||201202060927|||||||

所以基本上有数据行用管道 (|) 分隔,我想通过编写 bash 脚本来解析它们。

简单来说就是这个结构

  • 段 > 行
  • 字段> |之间的单元格字段 |
  • 组件 > 每个字段有(或没有)几个用 ^ 分隔的字段
  • 子组件 > 用 & 分隔

运行脚本的思路是:./script.sh filename command

命令应该类似于:MSH.2.3.4 或更短

含义:访问以 MSH 开头的字段,字段编号 2,组件编号 3,子组件 4

所以我的解析逻辑如下: 我想创建一个数组来存储文件中的每一行(段),如下所示:

#!/bin/bash

file_to_be_parsed=$1
command=$2
counter=0

#read the file and split it into lines (segments) by creating an array called segments which holds all the lines (segment) in it
#array segments[] holds every line/segment of the file indexed from 0 to X

while IFS= read -a segment; do
     segments[$counter]=$segment
     counter=$((counter+1)); 
done < $file_to_be_parsed

第二步:我的第二步是根据分隔符将每个数组成员进一步分开,我可以这样做:

IFS="|" read -r field <<< (here i can't figure out)

但我实际上无法在 bash 中创建二维数组,即使我搜索了很多。 然后我可以访问特定的字段...

那么有人可以帮助我如何进一步将这些数组成员分成字段...

【问题讨论】:

  • Bash 不能做嵌套数据结构。像 Python 这样的通用编程语言会更好。
  • @wjandrea 是的,Python 有专门的解析库,但我必须在 bash 脚本中进行。这是强制性的
  • 请编辑您的 Q 以显示来自示例输入的所需输出。祝你好运。
  • @Albion 嗯,所以我认为最好的办法是避免嵌套数据结构,只需为每个选定字段创建一个数组,即MSH.2.3.4,找到以@987654327 开头的行@,然后拆分它并选择第二个元素,然后拆分它,等等。
  • 查看Awk Tutorial 并查看-F 选项(用作-F\|,然后使用split(),使用split(string,targArr,"^")(用于分割的字符)。祝你好运。

标签: bash shell ubuntu awk


【解决方案1】:

这是一个经典的awk(标准 Linux gawk)问题。

这是一个简单的脚本,它使用awk 的内部split 函数验证输入参数并仅解析所需的字段、组件和子组件。

鼓励用户简化脚本输出布局。

至于脚本的参数,都是强制的(有些可能会被忽略),input.txt文件必须在最后。

input.txt

MSH|^~\&|Xatidok|V10.0.2.000|OSestra|x-tention|201203060855||ADT^A03|2914|P|2.3^AA&BB
EVN|A03|201203060855|201203060855|01|Fidani
PID|||00019380|2012049008^120005548^302830|PATIDOK-person^InRid^|Rudi|19111111|F|||Rose |A|Pens.
NK1||IRergrun^RROSlf^||Rose ^^Wels^^4600^A|07242123123|||||||||||||||||||||||||||||||
PV1||I|1212^G442^G442-||0|||||||||||2012049008|General|||||||||||||||||||12|||||201202060927|||||||

script.awk

BEGIN {FS="|"; componentSeperator="^"; subComponentSeperator="&"}
function readArgs() {
     if (passedReadArgs == 1) return;
     if (length(field) == 0) {print "Missing field string argument, exiting."; exit;}
     if (length(fieldNumber) == 0) {print "Missing fieldNumber number argument, exiting."; exit;}
     if (length(componentNumber) == 0) {print "Missing componentNumber number argument, exiting."; exit;}
     if (length(subComponentNumber) == 0) {print "Missing subComponentNumber number argument, exiting."; exit;}
     passedReadArgs = 1;
}
{
     readArgs();
     if ($0 !~ field) next;

     print "Arguments: "field, fieldNumber, componentNumber, subComponentNumber;

     print "field["fieldNumber"] = "$fieldNumber;

     split($fieldNumber, componentsArr, componentSeperator);
     if (length(componentsArr[componentNumber]) > 0) {
          print "component["componentNumber"] = "componentsArr[componentNumber];
          split(componentsArr[componentNumber], subComponentsArr, subComponentSeperator);
          if (length(subComponentsArr[subComponentNumber]) > 0) print "subComponent["subComponentNumber"] = "subComponentsArr[subComponentNumber];
     }
}

运行script.awk 脚本:

awk -f script.awk field="MSH" fieldNumber=11 componentNumber=2 subComponentNumber=2 input.txt

输出:

Arguments: MSH 12 2 2
field[12] = 2.3^AA&BB
component[2] = AA&BB
subComponent[2] = BB

Arguments: NK1 5 3 2
field[5] = Rose ^^Wels^^4600^A
component[3] = Wels


Arguments: PID 7 3 2
field[7] = Rudi

【讨论】:

    【解决方案2】:

    Fr puer bash-only 解决方案,可以使用 bash 数组将行拆分为字段、组件、子组件。前提是你不必在大型数据集上运行代码,应该没问题。

    考虑切换到更强大的引擎(awk、python、perl)来解决大问题。

    #! /bin/bash
    file=$1
    command=$2
       # Split command into key, so that items are key[0], key[1], ...
    IFS="." read -a k <<<"$command"
    
      # Look for matching line to k[0]
    while IFS='|' read -a fa ; do
      # Skip to next row if no match.
      [ "${fa[0]}" = "${k[0]}" ] || continue ;
      # Field
      v=${fa[${k[1]}-1]}
      # Component
      if [ "${#k[@]}" -gt 2 ] ; then
          IFS="^" read -a fb <<<"$v"
          v=${fb[${k[2]}-1]}
      fi
      # Sub component
      if [ "${#k[@]}" -gt 3 ] ; then
          IFS="&" read -a fc <<<"$v"
          v=${fc[${k[3]}-1]}
      fi
      echo "V=$v" ;
      break
    done <"$file"
    
    

    【讨论】:

    • 是的,除了子组件外,它有效,据我测试,它无法拆分最后一条规则,但总的来说它很棒。
    • 我们能否在 IFS 中使用两个条件,例如使用 IFS="|" 分割行和 IFS= 但同时?
    • @AlbionShala,对输入的子组件逻辑进行了小修复。
    • @AlbionShala, IFS 可以是多个字符。
    猜你喜欢
    • 2013-03-14
    • 2013-08-30
    • 2017-05-23
    • 1970-01-01
    • 2013-07-24
    • 2016-02-08
    • 1970-01-01
    • 2014-04-28
    • 2016-01-15
    相关资源
    最近更新 更多