【问题标题】:replace particular column value using awk if found如果找到,则使用 awk 替换特定列值
【发布时间】:2018-12-17 21:16:46
【问题描述】:

如何使用awk 查找和替换特定列的值?

比如说 -> 我有一个包含以下内容的文件测试:

"abc":"100"::"新"

"xyz":"200":"mob":"old"

"lmn":"300"::"新"

"pqr":"400":"mob2":"新"

现在,如果第 3 列为空白,那么我想替换 "N/A" 中的空白值,否则按原样打印该行,以便输出如下:

"abc":"100":"N/A":"新"

"xyz":"200":"mob":"old"

"lmn":"300":"N/A":"新"

"pqr":"400":"mob2":"新"

虽然我通过以下命令使用 awk 获得了输出:

awk -F":" '{
    if ( $3 == "")
        print $1":"$2":\"N\/A\":"$4
    else
        print $0
}' test

但在这里我使用了每个列的硬编码值,例如$1$2,因此如果其他示例中的空白列从第 3 列更改为 xyz,则必须再次在命令中更改相同的值。有没有其他方法可以使用 awk 而不使用列的硬编码值来获得相同的输出?感谢您的帮助。

【问题讨论】:

  • 您的意思是替换任何空列?或者只是任意列的空值,并且仅在该列上?
  • 替换应该只适用于那些第三列的值为空白的行...
  • 那么if the blank column changes in other example from 3rd to xyz then have to change the same in command again 是什么意思?听起来您是在说您想更改任何空白列,但随后您还说the replace should work only for those rows in which value of 3rd column is blank,这意味着应该只测试/更改第 3 列。很不清楚....

标签: bash shell awk scripting


【解决方案1】:

首先,让我们稍微简化一下你现在的程序:

awk -F: 'BEGIN {OFS=FS} {       
  if ( $3 == "") $3="N/A"
  print $0
}' test

现在我们可以使两件事可变:要测试的列和替换字符串。因此,程序的主体看起来像

if ( $fieldnumber == "" ) $fieldnumber=replacement

剩下要做的就是填写变量。如果您查看 awk 的手册页,您会看到选项 -v 允许我们为 awk 变量指定初始值。

awk -F: -v fieldnumber=... -v replacement=...

这允许你从任何你喜欢的地方填充这个变量 - 你的 shell 脚本的参数、环境变量等。

更新: 修复输出字段分隔符 (OFS) 更新:修复语法错误

【讨论】:

  • 我们需要添加 OFS=":" long 和 FS=":" 才能使您的代码正常工作。否则,发生替换的行,OFS 被替换为 [space] 而不是 ":"
  • 如果引用的字段中有冒号,则会失败。
  • @EdMorton :这是正确的,但这个问题已经存在于 OP 声称对她有用的专门解决方案中。这就是为什么我没有讨论这一点。从技术上讲,我们需要知道这是否真的是一个问题(也许引号没有作为分隔符的通常含义,唯一存在的分隔符是冒号),或者更有可能的是,要处理的文件位于CSV 格式,在这种情况下,使用 CSV 解析器的解决方案(例如,Ruby 或 Perl)将是合适的。
  • 失败并出现错误:awk: cmd.行:1:开始 {OFS=FS} awk:cmd。 line:1: ^ 语法错误
  • @EdMorton :啊,你是对的,像往常一样。我的错。修复了我的帖子。
【解决方案2】:

正确的方法是使用 GNU awk 进行 FPAT 和修改后的输入文件,以证明即使在引号字段中存在冒号时它也能正常工作:

$ cat tst.awk
BEGIN {
    FPAT = "([^:]*)|(\"[^\"]+\")"
    OFS = ":"
}
$3 == "" { $3 = "\"N/A\"" }
{ print }

$ cat file
"abc:def":"100"::"new"
"xyz":"200":"mob":"old"
"lmn":"123:456:300"::"new"
"pqr":"400":"mob2":"new"
"stu":"600":"foo::bar":"more"

$ awk -f tst.awk file
"abc:def":"100":"N/A":"new"
"xyz":"200":"mob":"old"
"lmn":"123:456:300":"N/A":"new"
"pqr":"400":"mob2":"new"
"stu":"600":"foo::bar":"more"

【讨论】:

    【解决方案3】:

    下面的 gawk 代码怎么样:

    BEGIN {
        FS=":"
        OFS=":"
    }
    {
        for(i=1; i<=4; i++) {
            if ($(i) == "") field[i] = "N/A"
            else field[i] = $(i)
        }
        if ($0 != "") print field[1],field[2],field[3],field[4]
    }
    

    -- 或者--
    也许下面的 bash 脚本要简单得多:

    #!/bin/bash
    export IFS=":"
    while read a b c d; do
        echo "${a:-N/A}:${b:-N/A}:${c:-N/A}:${d:-N/A}"
    done
    

    带有输入重定向,即this_bash_script.sh &lt; your_test_input.txt

    【讨论】:

    【解决方案4】:

    使用 GNU awk:

    awk -v RS='[:\n]'  '!NF{$0="\"N/A\""}{printf "%s%s",$0,RT}' test
    

    记录分隔符RS设置为捕获分号:之间的数据。

    如果没有任何字段(!NF),则设置想要的字符串。

    printf 语句为当前记录写入数据和记录分隔符RT

    【讨论】:

    • 如果引用的字段中有冒号,则会失败。
    • @EdMorton 不,它不会失败,无论是 OP 的例子还是你的例子。即使字段中有冒号,也不会进行基于NF 的测试,因此记录将保持不变并按原样打印。唯一有效的评论可能是记录没有正确拆分,但这与 OP 的情况无关。
    • 我发布的示例输入并非详尽无遗。当其中一个字段为"foo::bar" 时尝试您的脚本,您会看到该字段变为"foo:"N/A":bar"。我会将该案例添加到我的答案中的示例输入中。
    猜你喜欢
    • 2021-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-27
    • 2013-02-18
    • 1970-01-01
    • 2012-04-03
    • 1970-01-01
    相关资源
    最近更新 更多