【问题标题】:If column 4 is a match, sed replace column 9; else replace column 9 with something else如果第 4 列匹配,则 sed 替换第 9 列;否则将第 9 列替换为其他内容
【发布时间】:2021-10-18 02:56:58
【问题描述】:

当读取一个非常大的制表符分隔文件时:

.   .   .   A   .   .   .   .   3:.:.:20
.   .   .   B   .   .   .   .   4:.:30
.   .   .   C   .   .   .   .   5:.:.:40:.
.   .   .   D   .   .   .   .   .:.:.:.
.   .   .   A   .   .   .   .   7:.:.:21
.   .   .   B   .   .   .   .   .:.:.:.
.   .   .   D   .   .   .   .   .:.:.:.
.   .   .   C   .   .   .   .   .:.:.

我想将第 9 列保留为 .:.:.:.仅当第 4 列 = D。对于所有其他第 4 列值,我有一个单独的 sed 替换查询。所需的输出将如下所示:

.   .   .   A   .   .   .   .   1:.:.:80
.   .   .   B   .   .   .   .   1:.:80
.   .   .   C   .   .   .   .   1:.:.:80:.
.   .   .   D   .   .   .   .   .:.:.:.
.   .   .   A   .   .   .   .   1:.:.:80
.   .   .   B   .   .   .   .   1:.:.:80
.   .   .   D   .   .   .   .   .:.:.:.
.   .   .   C   .   .   .   .   1:.:80

我目前的伪代码是这样的:

if [column 4 = D]
    then
        # either replace or just keep original entry
        sed '/some replacement query/g' {file}
    else
        sed '/some other replacement query/g' {file}
fi

我在想如果我可以逐行读取文件,也许我可以采用这种方法?但后来我不断写信来更新一个新文件。

我不确定如何在 shell 环境中有效地完成这段时间。由于最终目标是创建一个大的最终文件,因此我当前的路线创建了两个不正确的不同版本。也许有一种方法可以在单个 sed 查询中执行我想要的操作?但这超出了我的专业范围。

编辑:sed 替换查询当前查看第 9 列并确定字段分隔符 (":") 的数量并将其替换为相应的值,即 3:.:.:20 变为 1:.:.:804:.:30 变为 1:.:80 , 和 5:.:.:40:. 变为 1:.:.:80:. 字段分隔符之间的值可以是数字 0-99 或“。”价值。但是,如果第 4 列 = N,则第 9 列必须变为(或在此示例中保持不变).:.:.:. 我当前的替换查询将 .:.:.:. 变为 1:.:.:80,这是我不想要的。

sed 's/\t\(\.\|[0-9]\):\(\.\|\([0-9]\+\)\):\(\.\|\([0-9]\+\)\):\(\.\|\([0-9]\+\)\)$/\t1:\.:\.:80/g; 
s/\t\(\.\|[0-9]\):\([0-9],[0-9]\):\(\.\|\([0-9]\+\)\):\(\.\|\([0-9]\+\)\):\(\.\|\([0-9]\+\)\)$/\t1:\.:\.:80:\./g; 
s/\t\(\.\|[0-9]\):\(\.\|\([0-9]\+\)\):\(\.\|\([0-9]\+\)\)$/\t1:\.:80/g'

【问题讨论】:

  • . 代表标签吗?特别是在最后一个领域?还是这些只是一些任意值?
  • . 都是其他列值的占位符,除了最后一个字段,一般是列的样子,所以./././. 是单列字符串
  • 您可以使用awk 还是仅限于sed
  • 我可以使用 awk,只要我仍然可以使用非常具体的 sed 查询进行替换...(如果这没有意义,请告诉我)
  • @anita awk 不明白 sed 的具体查询;它有自己的语言来编辑字符串。 (另一方面,sed 不理解列。)您想要进行的实际更改是什么?它可能在awk 中表达出来。

标签: shell unix awk sed


【解决方案1】:

awk 通常更擅长面向领域的东西,但也可以 用 sed 完成:除非第 4 列是 D 提取第 9 列,编辑并 追加为第 10 列,最后删除第 9 列。

EDIT:更新为使用 : 作为子字段分隔符(原为 /)。
EDIT2:澄清 \t 不是 POSIX 或对于 macOS。

sed -E -e '
/^([^\t]*\t){3}D\t/b            # special case: dont edit if D in column 4
h                               # copy line to hold space
s/^([^\t]*\t){8}([^\t]*).*/\2/  # set pattern space = column 9
s/^[^:]+/1/                     # set first subfield = 1
/([0-9]+){2}|[^:]+$/ s//80/     # set 2nd digit group or last subfield = 80
x                               # exchange pattern and hold spaces
G                               # append \n+hold space to pattern space
s/\n/\t/                        # replace \n with field separator
s/[^\t]*\t//9                   # delete (old) column 9
' -- file

其他 cmets:

  • 制表符是字段分隔符;如果您的 sed 不理解 \t (GNU sed 可以,但可能不在 macos 上)- 或者为了使脚本 POSIXly 正确- 用文字制表符替换 \t
  • s//80/ 中的空正则表达式重新应用上次使用的正则表达式(/…/
  • 使用-E 选项 ERE 扩展正则表达式

【讨论】:

  • 当我尝试这个时,仍然有 10 列,除了第 4 列 = D。谢谢你的所有解释
  • @anita:使用 GNU sed 4.7,我的脚本会产生完全所需的输出。您使用的是哪个sed? (sed --version)
  • @anita:如果您运行脚本并将其通过管道传输到 ` | 中,输出是什么? sed -n l |头-n 1`?最后我看到.\t.\t.\tA\t.\t.\t.\t.\t1:.:.:80$
  • 我在 mac 上,所以没有列出版本(即使在二进制文件中),这可能就是原因。让我在我的 linux 机器上快速尝试一下
  • 此答案适用于 linux (sed v. 4.2.2),但不适用于 mac
【解决方案2】:

我不确定您真的需要对这些映射进行硬编码,但您的问题中没有其他解释说明如何提出这些映射,因此,假设您这样做了,那么这将使用每个 Unix 机器上的任何 shell 中的任何 awk:

$ cat tst.awk
BEGIN {
    FS=OFS="\t"

    str2str[".:.:."]      = "1:.:80"
    str2str[".:.:.:."]    = "1:.:.:80"
    str2str[".:.:.:.:."]  = "1:.:.:80:."

    for (str in str2str) {
        re = str
        gsub(/\./,"(.|[0-9]+)",re)
        re2str["^("re")$"] = str2str[str]
    }
}
$4 != "D" {
    for (re in re2str) {
        if ($9 ~ re) {
            $9 = re2str[re]
        }
    }
}
{ print }

$ awk -f tst.awk file
.       .       .       A       .       .       .       .       1:.:.:80
.       .       .       B       .       .       .       .       1:.:80
.       .       .       C       .       .       .       .       1:.:.:80:.
.       .       .       D       .       .       .       .       .:.:.:.
.       .       .       A       .       .       .       .       1:.:.:80
.       .       .       B       .       .       .       .       1:.:.:80
.       .       .       D       .       .       .       .       .:.:.:.
.       .       .       C       .       .       .       .       1:.:80

【讨论】:

    【解决方案3】:

    使用您展示的示例,请尝试关注awk 程序。

    awk '$4!="D"{sub(/^[0-9]+|^\./,"1",$9);sub(/[0-9]+$|\.$/,"80",$9)} 1' Input_file
    

    如果您在 Input_file 中有 TAB 分隔值,则将 BEGIN 部分添加到上述程序中,如下所示:

    awk '
    BEGIN{FS=OFS="\t"}
    $4!="D"{
      sub(/^[0-9]+|^\./,"1",$9)
      sub(/[0-9]+$|\.$/,"80",$9)
    }
    1
    ' Input_file
    

    【讨论】:

      【解决方案4】:

      这是使用awk 的可能解决方案。

      cat file | awk '{if ($4 != "D") { sub($9, "Replaced the ninth field" ); print } else { print }}''
      

      如果第 4 个字段 ($4) 不是 D,这将用字符串替换第 9 个字段 ($9)。

      如果您足够了解最后一个字段的形状,也可以将$9 替换为正则表达式,例如/\d\/\.\/\d$ 之类的。不确定您是否还需要在其中进行反向引用替换。

      但这可能是一个开始。

      cat out.tsv | awk '{if ($4 != "D") { sub($9, "Replaced the ninth field" ); print } else { print }}'
      . . . A . . . . Replaced the ninth field
      .   .   .   B   .   .   .   .   Replaced the ninth field
      .   .   .   C   .   .   .   .   Replaced the ninth field
      .   .   .   D   .   .   .   .   ./././.
      .   .   .   A   .   .   .   .   Replaced the ninth field
      .   .   .   B   .   .   .   .   Replaced the ninth field
      .   .   .   D   .   .   .   .   ./././.
      .   .   .   C   .   .   .   .   Replaced the ninth field
      

      【讨论】:

      • 哎呀。我意识到我的 if/else 是错误的。如果第 4 列 不是 D,您需要替换。我将提交更新。
      • 不要使用cat获取数据栈到可以读取自身的程序。 awk 'code' out.csvporkmail.org/era/unix/award.html
      • 这会将输入中的所有制表符和空白链转换为单个空白字符。
      • if (x) { foo; print } else { print } = if (x) { foo } print。每当您发现自己在编写重复的代码(例如print)时,请花点时间考虑一下您是否真的需要这样做,或者是否有更好的方法来构建您的代码以避免它。
      猜你喜欢
      • 2015-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-13
      • 2018-04-23
      • 1970-01-01
      相关资源
      最近更新 更多