【问题标题】:Processing a delimited line in bash在 bash 中处理分隔线
【发布时间】:2020-01-21 01:37:51
【问题描述】:

给定一行输入,其中包含以空格分隔的“n”个参数。输入参数本身是可变的。输入是通过外部文件给出的。

我想根据正则表达式将特定元素移动到变量中。因此,我正在考虑首先声明一个指针变量来跟踪我所在的位置。此外,变量的赋值与数值顺序无关,根据输入的不同,某些变量可能会被完全跳过。

我目前的方法是使用 awk '{print $1}' file.txt 但是,并非所有元素都是固定的,我需要考虑可能不存在或可能有多个条目的元素。

更新:我找到了另一种方法。

file=$(cat /file.txt)
for i in ${file[@]}; do 
   echo $i >> split.txt; 
done

通过这种方式,我们得到的不是带有多个参数的单行,而是带有单个参数的多行。因此,我们现在可以使用var#=(grep --regexp="[pattern]" split.txt。现在我只需要弄清楚如何最好地使用正则表达式来过滤这个烂摊子。

让我举个例子。

我的输入字符串是:

RON KKND 1534Z AUTO 253985G 034SRT 134OVC 04/32

RON KKND 5256Z 143623G72K 034OVC 074OVC 134SRT 145PRT 13/00

RON KKND 2234Z CON 342523G CLS 01/M12 RMK

因此上述每个变量的分配将是:

var1=RON var2=KKND var3=1534Z var4=TRUE var5=FALSE var6=253985G varC=2 varC1=034SRT varC2=134OVC var7=04/32

var1=RON var2=KKND var3=5256Z var4=FALSE var5=FALSE var6=143623G72K varC=4 varC1=034OVC varC2=074OVC varC3=134SRT varC4=145PRT var7=13/00

var1=RON var2=KKND var3=2234Z var4=FALSE var5=TRUE var6=342523G varC=0  var7=01/M12

因此,第四个参数可能是 var4、var5 或 var6。 第五个参数可能是 var5、var6 或匹配其他条件。 第六个参数可能是也可能不是 var6。 var6 和 var7 之间可以通过将每个参数与*/* 匹配来确定

更进一步,var1、var2 和 var3 的输入位置是固定的,但之后我需要比较、排序和分配。此外,参数本身的字符长度可能会有所不同。要划分的每个部分的相对位置相对于其相邻部分是固定的。例如,在输入中 var7 永远不会在 var6 之前,如果 var4 和 var5 为真,则第 4 和第 5 个参数将始终为“AUTO CON” 某些段将始终是一个参数,而其他部分将不止一个。每个人的相对位置是已知的。对于每种模式,有些模式在特定位置具有特定字符,而另一些模式除了在序列中的位置外,可能没有任何标志。

所以我需要 awk 来识别指针变量,因为需要检查每个参数,直到找到特定的匹配项

#Check to see if var4 or var5 exists. if so, flag and increment pointer
pointer=4
if (awk '{print $$pointer}' file.txt) == "AUTO" ; then
   var4="TRUE"
   pointer=$pointer+1
else
   var4="FALSE"
fi
if (awk '{print $$pointer}' file.txt) == "CON" ; then
   var5="TRUE"
   pointer=$pointer+1
else
   var5="FALSE"
fi

#position of var6 is fixed once var4 and var5 are determined
var6=$(awk '{print $$pointer}' file.txt)
pointer=$pointer+1

#Count the arguments between var6 and var7 (there may be up to ten)
#and separate each to decode later. varC[0-9] is always three upcase 
# letters followed by three numbers. Use this counter later when decoding.
varC=0

until (awk '{print $$pointer}' file.txt) == "*/*" ; do

   varC($varC+1)=(awk '{print $$pointer}' file.txt)
   varC=$varC+1
   pointer=$pointer+1
done
#position of var7 is fixed after all arguments of varC are handled
var7=$(awk '{print $$pointer}' file.txt)
pointer=$pointer+1

我知道上面的语法不正确。问题是我该如何解决它。

var7 并不总是在输入行的末尾。但是 var7 之后的参数不需要处理。

实际上解释了我还没有理解的模式。我打算使用 case 语句将变量与正则表达式进行比较来处理这个问题。我不想使用 awk 直接解释模式,因为那样会变得非常混乱。我曾考虑使用for n in $string,但这样做意味着直接将每个参数与每个可能的组合进行比较(并且有多个段,每个段都有多个模式),这是不切实际的。我正在尝试将其分为两步。

【问题讨论】:

  • 很好尝试,但很抱歉不太清楚,请更清楚地添加示例输出和预期输出示例以及您需要输出的方式(逻辑)。
  • 现在应该更清楚了,我希望。
  • 据我了解,模式列表应应用于每一列每一行,并且匹配优先级很重要。输入行的顺序和列的内容可能会有很大的变化。请使用示例模式更新以匹配给定的输入。 旁注:使用 awk 是可行的,但工作量很大
  • 阅读这篇文章,了解如何在awk 中使用变量stackoverflow.com/questions/19075671/… 这个awk '{print $$pointer}' file.txt 是错误的,不会起作用。
  • 我尝试了上述方法,但每次命令都将整个输入视为整行,而不是像我试图做的那样将其分开。我知道awk '{print $$pointer}' file.txt 不正确。这就是这里的全部问题,我该如何解决它。

标签: bash shell parsing awk


【解决方案1】:

请尝试以下方法:

#!/bin/bash

# template for variable names
declare -a namelist1=( "var1" "var2" "var3" "var4" "var5" "var6" "varC" )
declare -a ary

# read each line and assign ary to the elements
while read -r -a ary; do
    if [[ ${ary[3]} = AUTO ]]; then
        ary=( "${ary[@]:0:3}" "TRUE" "FALSE" "${ary[4]}" "" "${ary[@]:5:3}" )
    elif [[ ${ary[3]} = CON ]]; then
        ary=( "${ary[@]:0:3}" "FALSE" "TRUE" "${ary[4]}" "" "${ary[@]:5:3}" )
    else
        ary=( "${ary[@]:0:3}" "FALSE" "FALSE" "${ary[3]}" "" "${ary[@]:4:5}" )
    fi
    # initial character of the 7th element
    ary[6]=${ary[7]:0:1}

    # locate the index of */* entry in the ary and adjust the variable names
    for (( i=0; i<${#ary[@]}; i++ )); do
        if [[ ${ary[$i]} == */* ]]; then
            declare -a namelist=( "${namelist1[@]}" )
            for (( j=1; j<=i-7; j++ )); do
                namelist+=( "$(printf "varC%d" "$j")" )
            done
            namelist+=( "var7" )
        fi
    done

    # assign variables to array elements
    for (( i=0; i<${#ary[@]}; i++ )); do
#       echo -n "${namelist[$i]}=${ary[$i]} "       # for debugging
        declare -n p="${namelist[$i]}"
        p="${ary[$i]}"
    done
#   echo "var1=$var1 var2=$var2 var3=$var3 ..."     # for debugging
done < file.txt

请注意,上面的脚本只是分配了 bash 变量,并没有打印任何内容 除非您明确地 echoprintf 变量。

【讨论】:

    【解决方案2】:

    已更新:此代码显示了如何多次根据模式匹配确定变量值。
    一个是纯 bash 的代码块,另一个是笨拙的方式

    bash 代码块需要关联数组支持,这在非常早期的版本中不可用
    还需要grep 来进行模式匹配
    GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)grep (GNU grep) 2.20 测试 在我学习why-is-printf-better-than-echo之后坚持printf而不是echo
    在使用 bash 时,我认为更好的做法是更具防御性

    #!/bin/bash
    declare -ga outVars
    declare -ga lineBuf
    declare -g NF
    #force valid index starts from 1
    #consistent with var* name pattern
    outVars=(unused var1 var2 var3 var4 var5 var6 varC var7)
    ((numVars=${#outVars[@]} - 1))
    declare -gr numVars
    declare -r outVars
    
    function e_unused {
        return
    }
    function e_var1 {
        printf "%s"  "${lineBuf[1]}"
    }
    function e_var2 {
        printf "%s"  "${lineBuf[2]}"
    }
    function e_var3 {
        printf "%s"  "${lineBuf[3]}"
    }
    
    function e_var4 {
        if [ "${lineBuf[4]}" == "AUTO" ] ;
        then
            printf "TRUE"
        else
            printf "FALSE"
        fi
    }
    function e_var5 {
        if [ "${lineBuf[4]}" == "CON" ] ;
        then
            printf "TRUE"
        else
            printf "FALSE"
        fi
    }
    function e_varC {
        local var6_idx=4
        if [ "${lineBuf[4]}" == "AUTO" -o "${lineBuf[4]}" == "CON" ] ;
            then
                var6_idx=5
        fi
    
        local var7_idx=$NF
        local i
        local count=0
        for ((i=NF;i>=1;i--));
        do
            if [ $(grep -cE '^.*/.*$' <<<${lineBuf[$i]}) -eq 1 ];
                then
                var7_idx=$i
                break
            fi
        done
        ((varC = var7_idx - var6_idx - 1))
        if [ $varC -eq 0 ];
            then
            printf 0
            return;
        fi
        local cFamily=""
        local append
        for ((i=var6_idx;i<=var7_idx;i++));
        do
            if [ $(grep -cE '^[0-9]{3}[A-Z]{3}$' <<<${lineBuf[$i]}) -eq 1 ];
                then
                ((count++))
                cFamily="$cFamily varC$count=${lineBuf[$i]}"
            fi
        done
        printf "%s %s"  $count "$cFamily"
    }
    
    function e_var6 {
        if [ "${lineBuf[4]}" == "AUTO" -o "${lineBuf[4]}" == "CON" ] ;
            then
            printf "%s"  "${lineBuf[5]}"
        else
            printf "%s"  "${lineBuf[4]}"
        fi
    }
    function e_var7 {
        local i
        for ((i=NF;i>=1;i--));
        do
            if [ $(grep -cE '^.*/.*$' <<<${lineBuf[$i]}) -eq 1 ];
                then
                printf "%s"  "${lineBuf[$i]}"
                return
            fi
        done
    }
    
    while read  -a lineBuf ;
        do
        NF=${#lineBuf[@]}
        lineBuf=(unused ${lineBuf[@]})
        for ((i=1; i<=numVars; i++));
            do
            printf "%s="  "${outVars[$i]}"
            (e_${outVars[$i]})
            printf " "
        done
        printf "\n"
    
    done <file.txt
    
    

    在下面的 awk 代码中使用了 gawk 特定扩展 Indirect Function Call
    代码为每个所需的输出变量分配一个函数名称。
    可以在其特定功能中应用不同的模式或其他转换
    这样做是为了避免大量的if-else-if-else
    并且更易于阅读和扩展。
    对于特殊的 varC 家族,函数 pick_varC 玩了一个把戏
    varC确定后,其值由多个输出字段组成。
    如果varC=2,则varC的值返回为2 varC1=034SRT varC2=134OVC
    这是附加所有跟随成员的 varC 的实际值。

    gawk '
        BEGIN {
            keys["var1"] = "pick_var1";
            keys["var2"] = "pick_var2";
            keys["var3"] = "pick_var3";
            keys["var4"] = "pick_var4";
            keys["var5"] = "pick_var5";
            keys["var6"] = "pick_var6";
            keys["varC"] = "pick_varC";
            keys["var7"] = "pick_var7";
        }
    
        function pick_var1 () {
            return $1;
        }
        function pick_var2 () {
            return $2;
        }
        function pick_var3 () {
            return $3;
        }
    
        function pick_var4 () {
            for (i=1;i<=NF;i++) {
                if ($i == "AUTO") {
                    return "TRUE";
                }
            }
            return "FALSE";
        }
    
        function pick_var5 () {
            for (i=1;i<=NF;i++) {
                if ($i == "CON") {
                    return "TRUE";
                }
            }
            return "FALSE";
        }
    
        function pick_varC () {
            for (i=1;i<=NF;i++) {
                if (($i=="AUTO" || $i=="CON")) {
                    break;
                }
            }
            var6_idx = 5;
            if ( i!=4 ) {
                var6_idx = 4;
            }
            var7_idx = NF;
            for (i=1;i<=NF;i++) {
                if ($i~/.*\/.*/) {
                    var7_idx = i;
                }
            }
            varC = var7_idx - var6_idx - 1;
            if ( varC == 0) {
                return varC;
            }
            count = 0;
            cFamily = "";
            for (i = 1; i<=varC;i++) {
                if ($(var6_idx+i)~/[0-9]{3}[A-Z]{3}/) {
                    cFamily = sprintf("%s varC%d=%s",cFamily,i,$(var6_idx+i));
                    count++;
                }
            }
            varC = sprintf("%d %s",count,cFamily);
            return varC;
        }
    
        function pick_var6 () {
            for (i=1;i<=NF;i++) {
                if (($i=="AUTO" || $i=="CON")) {
                    break;
                }
            }
            if ( i!=4 ) {
                return $4;
            } else {
                return $5
            }
        }
    
        function pick_var7 () {
            for (i=1;i<=NF;i++) {
                if ($i~/.*\/.*/) {
                    return $i;
                }
            }
        }
    
        {
            for (k in keys) {
                pickFunc = keys[k];
                printf("%s=%s ",k,@pickFunc());
            }
            printf("\n");
        }
        ' file.txt
    
    

    测试输入

    RON KKND 1534Z AUTO 253985G 034SRT 134OVC 04/32
    RON KKND 5256Z 143623G72K 034OVC 074OVC 134SRT 145PRT 13/00
    RON KKND 2234Z CON 342523G CLS 01/M12 RMK
    

    脚本输出

    var1=RON var2=KKND var3=1534Z var4=TRUE var5=FALSE varC=2  varC1=034SRT varC2=134OVC var6=253985G var7=04/32
    var1=RON var2=KKND var3=5256Z var4=FALSE var5=FALSE varC=4  varC1=034OVC varC2=074OVC varC3=134SRT varC4=145PRT var6=143623G72K var7=13/00
    var1=RON var2=KKND var3=2234Z var4=FALSE var5=TRUE varC=0  var6=342523G var7=01/M12
    

    【讨论】:

    • 我没有放模式(规则),因为有几十个需要检查,每个都取决于变量。我没有根据数十个变体检查每个人,而是试图将其全部分解,以便我可以仅根据该变量的相关变体检查每个变体。以上内容不能很好地处理应该完全跳过的特定模式,以及可能有多个条目需要使用相同规则(varC)进行检查的其他模式。上面也简化了,实际工作中我需要处理的变量比上面还要多。
    • 对不起,我没想到你在一行内有嵌套变量,我将重新考虑处理 varC 系列的代码结构。但是我没有得到won't handle well particular patterns that should be skipped entirely的情况
    • 将所有输入作为嵌套变量处理似乎是开销最小的最直接方法。有时在代码的特定点会有要跳过的模式,我可以使用与发现 var7 类似的方法找出那些模式。这是我尝试使用指针进行操作的另一个原因。我还更新了上面的内容以包含未使用 varC 的部分。如果管理内部变量比使用 awk 更容易,我们也可以使用 input=@(cat file.txt)
    • three letters followed by three numbers. 与测试数据不完全一致,我假设测试数据足够并且正则表达式正在寻找three UPCASE letters followed by three numbers
    • 所有字母都是大写的。我看到的唯一问题是未启用 gawk,最好以某种方式管理而不需要用户安装
    猜你喜欢
    • 2013-03-11
    • 1970-01-01
    • 2015-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-26
    • 1970-01-01
    相关资源
    最近更新 更多