【问题标题】:Bash split dataset into 1 line per pairBash 将数据集拆分为每对 1 行
【发布时间】:2018-11-13 13:39:03
【问题描述】:

首先抱歉,如果标题不是最清楚,我真的不知道如何更好地表达这个问题。

基本上我将数据接收到一个 bash 脚本中(我无法控制所述数据的格式),该脚本以以下格式到达:

(名称:Foo bar;UUID:;AnotherField:一些文本;TieredField:(编号:123;文本:更多文本;YetAnotherTier:(名称:somename;IP:125.214.21.4););NumericalData : 4; MoreInfo: 一些信息) ;

现在我要做的是遍历每个键/值对,以便我可以处理信息。显然删除前导/尾随“();”很简单。然后我想也许可以替换“;”使用换行符,但由于层级不同而中断。

关于层级,我不关心里面的循环,我只关心最高层所以说。因此:

TieredField:(编号:123;文本:更多文本;YetAnotherTier:(名称:somename;IP:125.214.21.4);)

就我而言是一对简单的。

预期结果:

名称:富吧 UUID: 另一个字段:一些文本 TieredField:(编号:123;文本:更多文本;YetAnotherTier:(名称:somename;IP:125.214.21.4);) 数值数据:4 更多信息:一些信息

由于我熟悉遍历文本块的行,将原始字符串转换为上述结果就足够了,尽管直接遍历上述每一行的答案也可以。

不太确定如何处理这个问题,所以任何方向都会受到赞赏。

【问题讨论】:

  • 解析器生成器怎么样?有很多,例如antlr.org。 “快速入门;示例”看起来很有希望。

标签: string bash loops


【解决方案1】:

有效:

# strip stdin up until first '(' is read
cut -d '(' -f2- | while read -r -n1 c; do
        case $c in
        ')') break; ;;
        # if read any char, this is field name, just print it
        [a-zA-Z]) echo -n "$c"; ;;
        # doublescore separates names from values
        :)
                echo -n ': '
                l=0
                while read -n1 c; do
                        case "$c" in
                        # we need to count levels of '(' ')'
                        '(') ((l++)); echo -n '('; ;;
                        ')') ((l--)); 
                             # if level gets under zero, break from here, look at `MoreInfo:` case
                             if ((l<0)); then 
                                 echo; break; 
                             else 
                                 echo -n ')'; 
                                 if ((l==0)); then 
                                     echo; break; 
                                 fi;
                             fi;
                             ;;
                        # ';' separetes the next field, but only if level is zero, cause otherwise those are nested fields
                        ';') 
                                if ((l==0)); then 
                                        echo; 
                                        break;
                                else 
                                        echo -n "$c"; 
                                fi;
                                ;;
                        *) echo -n "$c"; ;;
                        esac
                done;
                # if level is lower then zero, braek, look at `MoreInfo:` case
                if ((l<0)); then break; fi;
                ;;
        " ") ;;
        esac
done; 
cat >/dev/null

对于以下输入:

(Name: Foo bar; UUID: <blah-blah-0101>; AnotherField: Some text; TieredField: (Number: 123; Text: More Text; YetAnotherTier: (Name: somename; IP: 125.214.21.4) ; ) ; NumericalData: 4; MoreInfo: Some Information) ;

它产生输出:

Name: Foobar
UUID: <blah-blah-0101>
AnotherField: Sometext
TieredField: (Number:123;Text:MoreText;YetAnotherTier:(Name:somename;IP:125.214.21.4);)
NumericalData: 4
MoreInfo: SomeInformation

【讨论】:

  • 效果很好,只是去掉了空格。
【解决方案2】:

这是一个脚本

  • 读取原单行输入文件(input.txt)
  • 产生一个输出文件(output.txt)

更多:

  • 最初移除外部的两个大括号
  • 使用计数器计算内括号
  • 更改 IFS 以读取所有字符(包括空格)

#!/bin/bash

WITHOUT_OUTER="`cat input.txt | cut -d"(" -f2- | rev | cut -d")" -f2- | rev`;"
PAIR=''
CNT=0
NEWLINE=0
OLD_IFS=$IFS
IFS=''
while read -n1 C
do
  if [ "$C" == '(' ]
  then
    CNT=$((CNT+1))
  elif [ "$C" == ')' ]
  then
    CNT=$((CNT-1))
  fi
  if [ $CNT -eq 0 ]
  then
    if [ "$C" == ';' ]
    then
      PAIR="$PAIR\n"
      NEWLINE=1
    fi
  elif [ "$C" == ';' ]
  then
    PAIR="$PAIR$C"
  fi
  if [ "$C" != ";" ]
  then
    if [ ! $NEWLINE -eq 1 ]
    then
      PAIR="$PAIR$C"
    else
      NEWLINE=0
    fi
  fi
done < <(echo $WITHOUT_OUTER)
echo -e "$PAIR" > output.txt

格式化后的值在 output.txt 中。 cat output.txt 会告诉你结果:

Name: Foo bar
UUID: <blah-blah-0101>
AnotherField: Some text
TieredField: (Number: 123; Text: More Text; YetAnotherTier: (Name: somename; IP: 125.214.21.4) ; )
NumericalData: 4
MoreInfo: Some Information

【讨论】:

    【解决方案3】:

    这是非常低效的,但它会起作用 - 这个循环在将它们之间的任何内容打印为一个字符串之前查找第一个 '(' 和最后一个 ')'(我也假设不使用字符 '_'.. .):

    t=''
    n=0
    oIFS=$IFS
    IFS=';'
    for f in $(sed -e 's/^(//' -e 's/) ;$//')
    do
        if [[ $f = *'('* ]]; then
            t="${t}_ $f"
            let n++
        elif [[ $f = *')'* ]]; then
            t="${t}_ $f"
            let n--
            [[ $n -eq '0' ]] && echo ${t##_  }
        elif [[ $n -ne '0' ]]; then
            t="${t}_ $f"
        else
            echo ${f## }
        fi
    
    done | IFS=$oIFS sed 's/_/;/g'
    

    输出是:

    Name: Foo bar
    UUID: <blah-blah-0101>
    AnotherField: Some text
    TieredField: (Number: 123;  Text: More Text;  YetAnotherTier: (Name: somename;  IP: 125.214.21.4) ;  )
    NumericalData: 4
    MoreInfo: Some Information
    

    【讨论】:

    • 糟糕,刚刚看到@Kamil 的回答,这几乎是相同的。无论如何,强烈建议使用上面 cmets 建议的解析器。
    猜你喜欢
    • 2022-06-15
    • 2022-06-15
    • 2021-02-07
    • 1970-01-01
    • 2022-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-23
    相关资源
    最近更新 更多