【问题标题】:Trouble trimming whitespace when piping grep to awk管道 grep 到 awk 时无法修剪空格
【发布时间】:2015-11-27 19:05:11
【问题描述】:

我正在尝试为 grep 编写一个简单的包装器,以便将其输出以更易读的格式。这包括将匹配的字符串(出现在第二个冒号之后)放在新行上,并从匹配的字符串中修剪任何前导空格/制表符。

所以不要做以下事情:

$ grep -rnIH --color=always "grape" .

./apple.config:1:   Did you know that grapes are tasty?

我希望能够得到这个:

$ grep -rnIH --color=always "grape" . | other-command

./apple.config:1:   
Did you know that grapes are tasty?

我尝试了许多不同的方法来尝试做到这一点,包括使用 sed、awk 本身、替换、perl 等。要记住的重要一点是,我想从 $3 中删除前导空格,但 $3 可能不会实际上包含整个匹配的字符串(例如,如果匹配的字符串包含带有“:”字符的 url)。

到目前为止,我已经达到了以下几点。

$ grep -rnIH --color=always "grape" . | \
      awk -F ":" '{gsub(/^[ \t]+/, "", $3); out=""; for(i=4;i<=NF;i++){out=out$i}; print $1":"$2"\n"$3out}'

./apple.config:1:   
    Did you know that grapes are tasty?

gsub 旨在从第二个冒号之后出现的任何内容的开头修剪空格/制表符。然后 for 循环旨在构建一个变量,该变量由匹配字符串中可能已被字段分隔符“:”分割的任何其他内容组成。

非常感谢任何有助于正确修剪前导空格的帮助。

【问题讨论】:

  • other-command 可能是 sed 's/:[[:blank:]]*/\n/2' -- 可能需要 GNU sed 作为“2”标志
  • @glennjackman - 关闭!使用2 作为标志至少在 FreeBSD 的 sed 中有效。 GNUism 将在替换字符串中使用\n。制作这个sed $'s/:[[:blank:]]*/\\\n/2',它实际上可能是可移植的!

标签: bash awk sed grep


【解决方案1】:

在我看来,你想匹配一条线,在这种情况下,显示它

file:line_number
line with the match

对此,您可以直接使用awk

awk -v OFS=":" '/pattern/ {print FILENAME, NR;  print}' files*
  • FILENAME 代表你正在阅读的文件。
  • NR 代表行号。
  • OFS 代表输出字段分隔符,因此当您说 print a, b 时,分隔符是 :

要删除前导或尾随空格,您可以使用gsub(/(^ *| *$)/,""),这样它们看起来像:

awk -v OFS=":" '/and/ {print FILENAME, NR;  gsub(/(^ *| *$)/,""); print}' files*

看一个例子:

$ tail a b
==> a <==
hello
this is some test
         and i am done now

==> b <==
and here i am
done

现在让我们尝试匹配包含“and”的行:

$ awk -v OFS=":" '/and/ {print FILENAME, NR;  gsub(/(^ *| *$)/,""); print}' a b
a:3
and i am done now
b:4
and here i am

【讨论】:

  • 只是完全误读了这个问题,并认为你误读了它。对不起!
  • 此解决方案的唯一问题是原件对输出进行了着色,而事实并非如此。否则,这是一个很好的解决方案。我不确定 grep --color=always -B1 grape (或正在搜索的任何单词 - 答案中的“和”)作为后处理器是否会适当地解决问题。可能不是; grep 倾向于在文本块之间放置分隔符。 (例如,printf "%s\n" a b c b d b" | grep -B1 --color=always b 输出仅包含 --(两个破折号)的行。我想后置处理器:grep -v '^--$' 会处理这个问题,但它变得有点恶心。)
  • @JonathanLeffler 对于这种情况,有一个神奇的--no-group-separator 选项可以防止在匹配之间出现--。所以,是的,你的建议很棒! my solution | grep --no-group-separator --color=always -B1 grape 应该成功了。
  • 我更喜欢您的答案在添加颜色转义序列之前如何处理前导空白修剪(如果像@JonathanLeffler 建议的那样管道到 grep)。如果我像下面那样将其包装在 bash 函数中,您会建议如何防止 awk 处理认为它们是文件的子目录?我只能通过管道将文件指定到 awk 中,但这会创建您指出的双管道,这在我的解决方案中并不理想。很好的学习体验,谢谢。
  • @user1764386 这是一个非常好的问题,我现在没有解决方案。你可能会说awk '...' *,而awk在匹配目录时会显示一些错误,所以你可以用awk '...' * 2&gt;/dev/null将它们重定向到stderr。
【解决方案2】:

我最终使用了 grep、awk 和 sed 的组合来解决我的问题并生成所需的输出格式。当使用“--color=always”选项时,我想保留 grep 提供的彩色输出,这最初使我远离使用 awk 来执行文件内容匹配。

棘手的一点是彩色 grep 输出在意想不到的位置产生了颜色代码。因此,不可能从实际上以颜色代码开始的行中修剪前导空格。第二个棘手的部分是我需要确保包含 awk 文件分隔符(在我的例子中是“:”)的匹配字符串能够正确复制。

我制作了以下 bash 包装函数 finds() 以便快速递归地搜索目录中的文件内容。

#--------------------------------------------------------------#
# Search for files whose contents contain a given string.      #
#                                                              #
# Param1: Substring to recursively search for in file contents.#
# Param2: Directory in which to search for files. [optional].  #
# Return: 0 on success, 1 on failure.                          #
#--------------------------------------------------------------#
finds() {
    # Error if:
    # - Zero or more than two arguments were provided.
    # - The first argument contains an empty string.
    if [[ ( $# -eq 0  ) || ( $# -gt 2  ) || ( -z "$1" ) ]]
    then
        echo "About: Search for files whose contents contain a given string."
        echo "Usage: $FUNCNAME string [path-to-dir]"
        echo "* string     : string to recursively search for in file contents"
        echo "* path-to-dir: directory in which to search files. [OPTIONAL]"

        return 1 # Failure
    fi

    # (r)ecursively search, show line (n)umbers.
    # (I)gnore binaries, s(H)ow filenames.
    grep_flags="-rnIH"

    if [ $# -eq 1 ]; then # No directory given; search from current directory.
        rootdir="."
    else # Search from specified directory.
        rootdir="$2"
    fi

    # The default color code, with brackets
    # escaped by backslashes.
    def_color="\[m\[K"

    grep $grep_flags --color=always "$1" $rootdir | 
    awk '
    BEGIN {
        FS = ":"
    }
    {
        print $1":"$2
        out = $3
        for(i=4; i<=NF; i++) {
            out=out":"$i
        }
        print out
    }' |
    sed -e "s/$def_color\s*/$def_color/"

    return 0 # Success
}
  1. grep 用于在指定目录中包含的那些文件的内容中递归查找匹配的字符串。
  2. awk 用于打印“filename:linenumber”,然后构建一个包含其余参数的变量,由字段分隔符“:”分隔。这允许我们重新组合匹配字符串的其余部分,以防它被初始拆分(例如包含“http://”的 url)。
  3. sed 用于修剪输出行中的任何前导空格/制表符。在这里,它匹配默认颜色代码(后跟可变数量的空格)并将其替换为自身(没有尾随空格)。

设置正确的 def_color 值

我无法在上面的代码框中显示正确的 def_color 值(上面代码中显示的 \[m\[K 不正确)。要获得用于此变量的正确 ANSI 转义序列:

  1. 将 grep --color=always 的输出重定向到文本文件。

  2. 复制并粘贴下面突出显示的序列作为上面 finds() 函数中 def_color 的值。

  3. 在每个括号前添加一个“\”转义字符。

将彩色 grep 输出写入文本文件的代码:

$ cd orange_test/
$ cat orange1.txt
I like to eat oranges.
$grep -r --color=always "orange" . > ./grep_out.txt

使用函数

下面显示了函数产生的输出。注意,也可以在第二个参数中指定目录路径。

cheese_test/cheese1.txt

I like to eat cheese.

    Do you all like cheese?

   I like
when the cheese is
on my pizza.

you can find out more about
      cheese at http://cheeseisgood.com

cheesestick

【讨论】:

  • 很高兴有一个记录在案的答案。但是,我不认为拥有grep | awk | sed 是非常理想的。一般来说,每当你有这么多管道时,就挠头想想awk是否可以单独处理。正如我在回答中所说,仅awk 就可以以更强大的方式为您提供您正在寻找的所有输出。如果您需要颜色,请查看以下 Jonathan Leffler 的建议。
猜你喜欢
  • 1970-01-01
  • 2019-03-20
  • 1970-01-01
  • 1970-01-01
  • 2010-10-12
  • 2022-10-12
  • 1970-01-01
  • 2011-03-23
  • 1970-01-01
相关资源
最近更新 更多