【问题标题】:Cut column by column name in bash在bash中按列名剪切列
【发布时间】:2023-04-05 11:38:01
【问题描述】:

我想按名称指定一列(即102),找到该列的位置,然后使用cut -5,7- 之类的东西和找到的位置删除指定的列。

这是我的文件头 (delim = "\t"):

#CHROM  POS 1   100 101 102 103 107 108

【问题讨论】:

    标签: bash


    【解决方案1】:

    这个 awk 应该可以工作:

    awk -F'\t' -v c="102" 'NR==1{for (i=1; i<=NF; i++) if ($i==c){p=i; break}; next} {print $p}' file
    

    【讨论】:

    • OP 想要删除该列而不是打印它。
    • 在没有看到预期输出的情况下很难理解需求。 cut -d' ' -f1 命令通常只打印单列。
    • 这个 oneliner 对我很有帮助,我只想按名称提取字段。也许 SO 上的其他人要求解决这个不同的问题。
    【解决方案2】:

    这是一种可能的解决方案,没有只删除一列的限制。它被编写为 bash 函数,其中第一个参数是文件名,其余参数是要排除的列。

    rmcol() {
      local file=$1
      shift
      cut -f$(head -n1 "$file" | tr \\t \\n | grep -vFxn "${@/#/-e}" |
              cut -d: -f1 | paste -sd,) "$file"
    }
    

    如果您想选择而不是排除命名列,请将-vFxn 更改为-Fxn

    这几乎肯定需要某种解释。该函数的前两行只是从参数中删除文件名并将其存储以供以后使用。然后cut 命令将选择适当的列;列号是使用以下复杂管道计算的:

    head -n1 "$file" |  # Take the first line of the file
    tr \\t \\n       |  # Change all the tabs to newlines [ Note 1]
    grep                # Select all lines (i.e. column names) which
         -v             #   don't match
           F            #   the literal string
            x           #   which is the complete line
             n          #   and include the line number in the output
         "${@/#/-e}" |  # Put -e at the beginning of each command line argument,
                        #   converting the arguments into grep pattern arguments (-e)
    cut -d: -f1      |  # Select only the line number from that matches
    paste -sd,          # Paste together all the line numbers, separated with commas.
    

    【讨论】:

    • 当您想删除而不是保留列时,为什么不询问cut--complement 您的选择?
    • @DouwevanderLeest:这也有效,至少在 Gnu cut 中是这样。我不认为 BSD cut 有这个选项。但是删除v 很容易:-)
    【解决方案3】:

    在 bash 中使用 for 循环:

    C=1; for i in $(head file -n 1) ; do if [ $i == "102" ] ; then break ; else C=$(( $C + 1 )) ; fi ; done ; echo $C
    

    还有一个完整的脚本

    C=1
    for i in $(head in_file -n 1) ; do
        echo $i
        if [ $i == "102" ] ; then
            break ;
        else
            echo $C
            C=$(( $C + 1 ))
        fi
    done
    cut -f1-$(($C-1)),$(($C+1))- in_file
    

    【讨论】:

    • 我认为 $i == "102" 应该是 $i = "102". ==` 给我这样的错误:stackoverflow.com/questions/2011160/unexpected-operator-error
    • 正确,这是 POSIX。
    • 使用 bash,您可以简单地使用 (( C++ )) 代替 C=$(( $C + 1 )),甚至可以。 echo $(( C++ )) 作为单个操作。
    • 是否有一种简单的方法可以为多个列执行此操作?
    • 在 bash 4.x 中,是的,有这么简单的方法——你可以创建一个列名到位置的关联数组。
    【解决方案4】:

    在不循环列的情况下尝试解决方案,我得到:

    #!/bin/bash
    pick="$1"
    titles="pos 1 100 102 105"
    
    tmp=" $titles "
    tmp="${tmp%% $pick* }"
    tmp=($tmp)
    
    echo "column ${#tmp[@]}"
    

    如果找不到列名,则会错误地报告最后一列。

    【讨论】:

      【解决方案5】:

      试试这个小的 awk 实用程序来剪切特定的标题 - https://github.com/rohitprajapati/toyeca-cutter

      示例用法 -

      awk -f toyeca-cutter.awk -v c="col1, col2, col3, col4" my_file.csv

      【讨论】:

        猜你喜欢
        • 2022-01-11
        • 2021-09-11
        • 2014-07-01
        • 1970-01-01
        • 2022-01-26
        • 2016-10-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多