【问题标题】:Always include first line in grep总是在 grep 中包含第一行
【发布时间】:2012-04-15 16:46:59
【问题描述】:

我经常 grep CSV 文件的第一行有列名。因此,我希望 grep 的输出始终包含第一行(以获取列名)以及与 grep 模式匹配的任何行。最好的方法是什么?

【问题讨论】:

    标签: bash grep


    【解决方案1】:

    做事

    head -1 <filename> 
    

    然后执行grep

    【讨论】:

    • 不完整。您还希望将输出合并到单个流中(在组或子shell 中执行),并且您希望确保grep 也不会意外匹配文件的第一行。跨度>
    • 是的,当然,但你可以只做head -1 &lt;filename&gt;tail -1 +1 | grep ... - 似乎比这里的许多想法简单得多 - 这就是我要说的全部
    【解决方案2】:

    您可以为其中一个列名添加替代模式匹配。如果一个列被称为 COL 那么这将起作用:

    $ grep -E 'COL|pattern' file.csv
    

    【讨论】:

    • 如果您没有严格控制第一行的内容,它也可能匹配文件后面的意外行。
    【解决方案3】:

    您可以使用sed 而不是grep 来执行此操作:

    sed -n -e '1p' -e '/pattern/p' < $FILE
    

    如果第一行恰好包含该模式,这将打印两次。

    -n 告诉sed 默认情况下不要打印每一行。
    -e '1p' 打印第一行。
    -e '/pattern/p' 打印与模式匹配的每一行。

    【讨论】:

    • 使用; 分割:sed -n '1p;/pattern/p'
    • 如果第一行包含图案,不会打印两次吗?
    • sed '1b;/pattern/!d' 将解决 print the first line twice 问题。
    【解决方案4】:

    grep 并没有真正的行号概念,但 awk 有,所以这里有一个输出行包含“Incoming”的示例 - 第一行,不管它是什么:

    awk 'NR == 1 || /Incoming/' foo.csv
    

    你可以写一个脚本(有点过分,但是)。我做了一个文件,grep+1,把这个放进去:

    #!/bin/sh
    pattern="$1" ; shift
    exec awk 'NR == 1 || /'"$pattern"'/' "$@"
    

    现在可以:

    ./grep+1 Incoming
    

    edit:删除了“{print;}”,这是 awk 的默认操作。

    【讨论】:

    • 你好,Alex -- 我喜欢脚本的想法,但可能会稍微修改它以实际使用 grep 而不是 awk,以便可以使用 grep 的其他命令行参数:read; printf '%s\n' "$REPLY"; grep "$@"。这种方法的主要问题是,如果 args 包含文件名,则需要将它们解析出来以进行本地处理。
    • 处理引用的更简单方法:awk -v patt="$pattern" 'NR==1 || $0 ~ patt'
    • 对,查尔斯。一个好的解决方案是保留所有 grep 的选项,以及从标准输入或文件名读取。然而,这个问题听起来更像是在寻找一个单行,甚至像另一个提供的损坏的行,文件名使用 2 美元(因此它仅适用于 1 个文件),但以错误反馈为代价。
    • Glenn:在“-v”上——提问者没有包括他的 Unix 变体。所有版本的 grep 都有 -v 吗? :-)
    • @AlexNorth-Keys,即awk -v,是的,该功能包含在One True Awk 中,即in POSIX。当然,grep -v 也是in POSIX。 :)
    【解决方案5】:

    sed:

    sed '1p;/pattern/!d' input.txt
    

    awk:

    awk 'NR==1 || /pattern/' input.txt
    

    grep1:

    grep1() { awk -v pattern="${1:?pattern is empty}" 'NR==1 || $0~pattern' "${2:-/dev/stdin}"; }
    

    【讨论】:

    • 请注意 sed 版本显示第一行两次。使用'1{p;d}; 而不是1p; 来避免这种情况。
    • 我喜欢可以使用 sed,但我绝不会向新手推荐它古怪的语法。 awk 版本更容易解释,而 sed 版本则更难理解。即时错误:?不过,在函数中非常酷。
    • 要打印 多行 从乞求:sed '1,3p;/pattern/!d'awk 'NR&lt;4 || /pattern/' - 将打印前 3 行。
    • 如何调整grep1 以使kubectl get pods -n foo | grep1 bar 工作而不是返回filename is empty
    【解决方案6】:

    这是一个非常通用的解决方案,例如,如果您想在保留第一行的同时对文件进行排序。基本上,“按原样传递第一行,然后对其余数据做任何我想做的事情 (awk/grep/sort/whatever)。”

    在脚本中试试这个,也许叫它keepfirstline(别忘了chmod +x keepfirstline,把它放在你的PATH中):

    #!/bin/bash
    IFS='' read -r JUST1LIINE
    printf "%s\n" "$JUST1LIINE"
    exec "$@"
    

    可以这样使用:

    cat your.data.csv | keepfirstline grep SearchTerm > results.with.header.csv
    

    或者,如果您想使用 awk 进行过滤

    cat your.data.csv | keepfirstline awk '$1 < 3' > results.with.header.csv
    

    我经常喜欢对文件进行排序,但将标题保留在第一行

    cat your.data.csv | keepfirstline sort
    

    keepfirstline 执行它给出的命令 (grep SearchTerm),但仅在读取并打印第一行之后。

    【讨论】:

    • 谢谢!有没有办法保留 grep 红色?
    • grep --color=yes 无论如何都应该让 grep 变得丰富多彩。你可能会发现grep --color 在很多情况下就足够了
    【解决方案7】:

    另一种选择:

    $ cat data.csv | (read line; echo "$line"; grep SEARCH_TERM)
    

    例子:

    $ echo "title\nvalue1\nvalue2\nvalue3" | (read line; echo "$line"; grep value2)
    

    输出:

    title
    value2
    

    【讨论】:

    • 一个非常简单优雅的解决方案,仍然使用grep,无需创建函数或'hack'。
    【解决方案8】:

    所以,我不久前发布了一个完全不同的简短答案。

    但是,对于那些在获取所有相同选项方面看起来像 grep 的命令的人来说(尽管此脚本要求您在涉及 optarg 时使用长选项),并且可以处理文件名中的奇怪字符,等等等等……把它拆开玩得开心。

    本质上它是一个总是发出第一行的 grep。如果您认为没有匹配行的文件应该跳过发出第一(标题)行,那么这留给读者作为练习。我保存为grep+1

    #!/bin/bash
    # grep+1 [<option>...] [<regex>] [<file>...]
    # Emits the first line of each input and ignores it otherwise.
    # For grep options that have optargs, only the --forms will work here.
    
    declare -a files options
    regex_seen=false
    regex=
    
    double_dash_seen=false
    for arg in "$@" ; do
        is_file_or_rx=true
        case "$arg" in
            -*) is_file_or_rx=$double_dash_seen ;;
        esac
        if $is_file_or_rx ; then
            if ! $regex_seen ; then
                regex="$arg"
                regex_seen=true
            else
                files[${#files[*]}]="$arg"     # append the value
            fi
        else
            options[${#options[*]}]="$arg"     # append the value       
        fi
    done
    
    # We could either open files all at once in the shell and pass the handles into
    # one grep call, but that would limit how many we can process to the fd limit.
    # So instead, here's the simpler approach with a series of grep calls
    
    if $regex_seen ; then
        if [ ${#files[@]} -gt 0 ] ; then
            for file in "${files[@]}" ; do
                head -n 1 "$file"
                tail -n +2 "$file" | grep --label="$file" "${options[@]}" "$regex" 
            done
        else
            grep "${options[@]}"   # stdin
        fi
    else
        grep "${options[@]}"   # probably --help
    fi
    
    #--eof
    

    【讨论】:

    • 达菲应该喜欢这个。
    【解决方案9】:

    所有答案都是正确的。对于包括第一行在内的命令(而不是文件)的输出的另一种想法可以这样做;-)

    df -h | grep -E '(^Filesystem|/mnt)'  # <<< returns usage of devices, with mountpoint '/mnt/...'
    ps aux | grep -E '(^USER|grep)'       # <<< returns all grep-process
    

    grep 的-E 选项启用其正则表达式模式。我们 grep 使用的字符串 | 可以解释为“或”,因此我们在 df-exmaple 中查找行:

    • Filesystem 开头(第一个子表达式中的前导'^'表示“行以”开头)
    • 和包含/mnt 的行

    另一种方法可能是将输出通过管道传输到 tempfile 并像其他帖子中所示那样对内容进行 grep。如果您不知道第一行的内容,这可能会有所帮助。

    head -1 <file> && grep ff <file>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-18
      • 2017-10-10
      • 1970-01-01
      • 2012-10-06
      • 1970-01-01
      • 2021-08-13
      • 2014-02-23
      • 1970-01-01
      相关资源
      最近更新 更多