【问题标题】:Bash Script: printing grep'd lines from fileBash Script:从文件中打印 grep 行
【发布时间】:2015-04-05 11:44:04
【问题描述】:

我正在尝试 grep 匹配某些模式的行,然后尝试打印那些匹配的行。

#!/bin/bash

file=/path/to/some/file
pattern=socket
if [ -f $file ]; then
    lines=`grep -i "$pattern" $file`
# Case 1
    for x in $lines; do   # <--- isn't this an array
        echo "$x"                                                                                                                                                                                                                                                                
        done
# Case 2
    while read -r line_a; do
        echo "$line_a"
        done <<< "$lines"
fi

输出:
案例 1:每行都打印这些行中的单个单词,而不是完整的行。
案例 2:打印单独的行。

问题:
为什么 case 1 不在一行上打印整行,而不是在每一行上打印该行中的单个单词? $lines 不是一个字符串数组(在我的例子中是行)吗?

【问题讨论】:

  • 不,它不是一个数组,您可以使用反引号将grep 的结果捕获为一个大字符串。我猜 for 循环将空格视为记录分隔符,因此每个单词都被视为一个元素

标签: bash grep


【解决方案1】:

$lines 不是字符串数组吗(在我的例子中是行)?

没有; $lines 是一个标量 字符串变量,其中包含从命令grep -i "$pattern" $file 捕获的整个输出 - 换句话说:单个字符串可能包含多行。

为什么 case 1 不在一行上打印整行而不是在每一行上打印该行中的单个单词?

因为您引用了变量$lines未引用,这意味着它受制于分词(以及其他所谓的shell expansions)。 p>

分词意味着输入被空格(甚至跨行)分割成标记,每个标记分别传递给for循环。


对于单个输入字符串,即使您将 $IFS 设置为 $'\n',也没有安全的方法可以使用 for 迭代其行,因为这些行仍然会受到路径名扩展(通配符)的影响;即,如果一行包含一个恰好是有效 glob 的子字符串(文件名模式,例如,*),它将扩展为匹配的文件名。

for 循环中使用 array 行确实有效,但需要用 未修改 输入行填充它;使用lines=($(grep -i "$pattern" "$file")) 填充数组不是一种选择,原因与上述相同。


您有两个选择,都使用process substitution 来捕获grep 命令的输出:

(a) 如果您确实需要将所有行预先读入内存,请将它们健壮地读入数组,如下所示:

IFS=$'\n' read -d '' -ra lines < <(grep -i "$pattern" "$file") 

在 bash 4+ 中,您可以改用 readarray -t lines ...

然后在for循环中处理它们,如下所示:

 for line in "${lines[@]}"; do # double quotes prevent word splitting and globbing
    echo "$line"
 done 

(b) 否则,使用while循环直接逐行读取grep的输出:

while IFS= read -r line; do
    echo "$line"
done < <(grep -i "$pattern" "$file")

【讨论】:

  • 感谢您的详细解释。我最近开始研究 bash,在哪里可以了解何时使用 - 单/双引号、反引号、花括号、方括号、圆括号等。?
  • stackoverflow.com/a/23140961/45375 会给你一个快速的报价介绍; shell expansions 讨论了 shell 执行的所有扩展(替换)。 mywiki.wooledge.org/BashGuide 通常是一个很棒的 Bash 资源。另外,不要忘记man bash,它包含所有相关信息,但内容密集且不易阅读。
  • 您能解释一下 (b),IFS 的值是多少?我可以单独阅读这些行,但在这些行中,字符“n”的任何外观都被替换为“”(空格)。
  • IFS=(后跟一个空格)表示IFS设置为空字符串,这停用分词,意味着每个输入行被整体读入未修改$line
  • 我无法解释将“n”替换为空格。您可以使用相关代码和更多详细信息创建一个新问题。
【解决方案2】:

您当前正在使用反引号捕获输出,它将整个输出视为一个大字符串。如果要将其捕获为数组,请使用以下符号

lines=($(grep -i "$pattern" $file))

但是,默认记录分隔符是空格,因此每个数组元素将是一个单词,而不是来自grep 输出的整行。您可以通过(临时)更改记录分隔符IFS 以在换行符处拆分来规避此问题。整个解决方案如下所示

IFS=$'\n'
lines=($(grep -i "$pattern" $file))
for x in ${lines[@]}; do
    echo $x
done

请注意,您现在已经更改了 shell 的 IFS,您可能希望将其重置为旧值。如您所见,这种方法很可能不是最适合您的问题的方法,但我将其发布在这里是为了回答您的原始问题

【讨论】:

  • lines=($(grep -i "$pattern" $file))....还是不行。它将空格分隔的字符存储为数组的元素。
  • 您是否将IFS 正确设置为换行符?
  • 是的,这就是问题所在。谢谢!
  • 如果你不双引号 ${lines[@]},它会受到分词再次 - 虽然这在这种情况下没有害处 - 因为 $IFS 是仍然设置为\n - 这是不必要的。然而,更重要的是,使用lines=($(grep -i "$pattern" $file)) 将始终使grep 命令输出的行受到路径名扩展(通配)的影响,这通常是不希望的。
猜你喜欢
  • 2017-01-18
  • 2012-11-23
  • 2012-07-04
  • 2013-10-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多