我怎样才能一次 grep 两次?

我怎样才能一次 grep 两次?

有没有一种方法可以避免grep在文件中执行两次而只需一次性填充变量?文件很小,所以没什么大不了的,我只是想知道是否可以一次性完成

FIRST_NAME=$(grep "$customer_id" customer-info|cut -f5 -d,)
LAST_NAME=$(grep "$customer_id" customer-info|cut -f6 -d,)

答案1

您可以使用 shell 字符串替换 grep 一次并分割两次:

NAME=$(grep "$customer_id" customer-info | cut -f5,6 -d,)
FIRST_NAME=${NAME%,*}
LAST_NAME=${NAME#*,}

或者,对于 bash,使用进程替换:

IFS=, read FIRST_NAME LAST_NAME < <(grep "$customer_id" customer-info | cut -f5,6 -d,)

read将拆分输入IFS并将第一个值分配给 ,FIRST_NAME将其余值分配给LAST_NAME。使用进程替换和重定向允许您在不使用子 shell 的情况下传递to< <(...)的输出。grep ... | cut ...read

答案2

最简单的方法是将整个记录放入一个变量中,然后使用cut它。

RECORD=$(grep "$customer_id" customer-info)
FIRST_NAME=$(echo "$RECORD"|cut -f5 -d,)
LAST_NAME=$(echo "$RECORD"|cut -f6 -d,)

我个人也建议使用更具体的正则表达式。如果您的客户 ID 始终位于行的开头,您可以编写grep '^'"$customer_id"而不是grep "$customer_id"要求匹配位于行的开头。否则,您可能会选取与客户 ID 匹配的文本恰好出现在记录中其他位置的记录。

答案3

您可以awk与 bash 结合使用read

read -r FIRST_NAME LAST_NAME <<< $(awk -F, -v cid="$customer_id" '$0~cid{print $5,$6}' customer-info)

-F告诉 awk 使用逗号作为字段分隔符

-v将 awk 变量设置cid为 shell 变量$customer_id

如果该行与 匹配$customer_id,awk 将打印第 5 个和第 6 个字段,并且这些字段将被分配变量FIRST_NAMELAST_NAME

如果名字 ($5) 包含空格(例如:a,b,c,d,Sarah Jane,Smith),请在字段之间添加-v OFS=,输出awk逗号,并添加前缀readwithIFS=,以使其在逗号处拆分。

此外,还awk可以仅在特定字段中进行搜索,例如'$3~cid{print..}'-- 并且可以匹配该字段全部的'$3~"^"cid"$"{print...}'如果这对你的 ID 很重要,则字段。

答案4

现有的答案都将输出存储在内存中(在变量中)并重放两次。如果您想制作一个可以接受任意大输入并对其执行两项任务的通用包装器,这就是一个问题。相反,输出流可以被复制并流式传输到两个命令中。

就我而言,目的是过滤输出流中可能任意长的标头(第一行)和特定(一组)行。一个简单的例子是显示磁盘空间使用情况:

$ df -h | tee >(head -1 >&2) | grep '/$'
Filesystem    Size  Used Avail Use% Mounted on
/dev/sda1     202G  145G   57G  72% /

替换df -h为您要使用的命令,并将head -1和替换grep '/$'为您要对其应用的两个命令。两者的输出都将显示在您的终端中,尽管前一个命令的输出可能会显示在后者之后。

这是如何运作的?

  • 该程序tee“将标准输入复制到每个[参数],也复制到标准输出。”因此它可以通过使用将 stdin 上的输出发送到 stdout 和 stderr command | tee /dev/stderr
  • command >(command2)语法被 bash 替换为参数,因此command /dev/fd/63将被执行。当command尝试写入时/dev/fd/63,它将最终出现在 的输入(stdin)中command2。这称为进程替换(参见参考资料man bash)。
  • 由于tee同时写入参数(我们将命令替换作为参数传递)和标准输出,因此我们可以添加另一个管道并执行另一个命令。所以现在我们有了command | tee >(command2) | command3
  • 最后,由于 command2 将输出到 stdout,并且 stdout 通过管道传输到command3,因此我们(在我的示例中)将 grep 标题行。这不是我们想要的:我们想要显示它。由于我们没有通过管道传输 stderr,因此将输出重定向到 stderr 是在终端中显示它的一种简单方法,即我们添加>&2,结果是command | tee >(command2 >&2) | command3.

有一个问题:输出可能是任意顺序的。根据宇宙射线的不同,我们可能会看到上述或以下情况:

$ df -h | tee >(head -1 >&2) | grep '/$'
/dev/sda1     202G  145G   57G  72% /
Filesystem    Size  Used Avail Use% Mounted on

解决这个问题的一种老套但可靠的方法(而不是一些过度设计的、不老套的方法)是在第二个命令中添加一个短暂的睡眠;就像是:

$ df -h | tee >(head -1 >&2) | sleep 1; grep '/$'

但是等等,这会破坏第二个命令 ( grep),因为现在输出从teeto进行管道传输sleepgrep并将无限期地等待输入。为了解决这个问题,我们添加一个子 shell:

$ df -h | tee >(head -1 >&2) | (sleep 0.01; grep '/$')
Filesystem    Size  Used Avail Use% Mounted on
/dev/sda1     202G  145G   57G  72% /

现在输出不是重定向到grep而是重定向到我们的子 shell。由于sleep不从中读取(它不消耗流),因此仍然可以grep读取。现在,只要head在 0.01 秒内输出(加上 grep 方面的一点开销),它就可以可靠地工作,这对现代系统来说是一个公平的赌注,而且足够短,不会被用户注意到。

由于我想制作一些同时采用某些命令的标头和输出的东西,因此我们可以将其概括为:

function grabheader {
    tee >(head -1 >&2)
}

由于tee函数中的命令只会从 stdin 读取并输出到 stdout,因此当您将其用作df -h | grabheader | grep '/$'.但由于我们希望它按顺序排列,因此我们需要延迟将其发送到标准输出:

function grabheader {
    tee >(head -1 >&2) | (sleep 0.01; cat)
}

cat这里只是确保传递到标准输入的任何内容都会再次进入标准输出。通过不传递任何参数并且不添加重定向,它就能做到这一点。用法:

$ df -h | grabheader | grep '/$'
Filesystem    Size  Used Avail Use% Mounted on
/dev/sda1     202G  145G   57G  72% /

当然,在 的特殊情况下df,这可以做得更简单:

$ df -h /
Filesystem    Size  Used Avail Use% Mounted on
/dev/sda1     202G  145G   57G  72% /

但现在我们有了使用任何命令执行此操作的通用方法。

相关内容