bash sed 在 while 循环中失败

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8654576/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 01:13:39  来源:igfitidea点击:

bash sed fail in while loop

bashsed

提问by meAtStackOverflow

#!/bin/bash
fname=
rname=
echo "$(<$fname)" | while read line ; do
    result=`echo "$(<$rname)" | grep "$line"; echo $?`
    if [ $result != 0 ]
    then
        sed  '/$line/d' $fname > newkas
    fi 2> /dev/null
done

Hi all, i am new to bash.

大家好,我是 bash 新手。

i have two lists one older than another. I wish to compare the names on 'fname' against 'rname'. 'Result' is the standard out put which i will get if the name is still available in 'rname'. if is not then i will get the non-zero output. Using sed to delete that line and re route it to a new file.

我有两个列表,一个比另一个早。我希望将“fname”上的名称与“rname”进行比较。“结果”是标准输出,如果名称在“rname”中仍然可用,我将获得该输出。如果不是,那么我将获得非零输出。使用 sed 删除该行并将其重新路由到新文件。

I have tried part by part of the code and it works until i add in the while loop function. sed don't seems to work as the final output of 'newkas' is the same as the initial input 'fname'. Is my method wrong or did i miss out any parts?

我已经尝试了部分代码,它可以工作,直到我添加了 while 循环函数。sed 似乎不起作用,因为“newkas”的最终输出与初始输入“fname”相同。我的方法是错误的还是我错过了任何部分?

回答by sorpigal

Part 1: What's wrong

第 1 部分:出了什么问题

The reason your sedexpression "doesn't work" is because you used single quotes. You said

您的sed表达“不起作用”的原因是因为您使用了单引号。你说

sed  '/$line/d' $fname > newkas

Supposing fname=input.txt'and line='example text'this will expand to:

假设fname=input.txt'line='example text'这将扩大到:

sed  '/$line/d' input.txt > newkas

Note that $lineis still literally present. This is because bashwill not interpolate variables inside single quotes, thus sedsees the $literally.

请注意,这$line仍然是字面上的存在。这是因为bash不会在单引号内插入变量,因此sed$字面意思。

You could fix this by saying

你可以通过说来解决这个问题

sed  "/$line/d/" $fname > newkas

Because inside double quotes the variable will expand. However, if your sedexpression becomes more complicated you could run into difficulty in cases where bash interprets things which you intended to be interpreted by sed. I tend to use the form

因为在双引号内,变量会扩展。但是,如果您的sed表达式变得更加复杂,那么在 bash 解释您打算由sed. 我倾向于使用表格

sed '/'"$line"'/d/' $fname > newkas

Which is a bit harder to read but, if you look carefully, single-quotes everything I intend to be part of the sedexpression and double quotes the variable I want to expand.

这有点难读,但是,如果你仔细看,单引号我打算成为sed表达式的一部分,双引号我想要扩展的变量。

Part 2: How to improve it

第 2 部分:如何改进它

Your script contains a number things which could be improved.

您的脚本包含许多可以改进的内容。

echo "$(<$fname)" | while read line ; do
    :
done

In the first place you're reading the file with "$(<$fname)"when you could just redirect the stdin of the whileloop. This is a bit redundant, but more importantly you're piping to while, which creates an extra subshell and means you can't modify any variables from the enclosing scope. Better to say

首先,"$(<$fname)"当您可以重定向while循环的标准输入时,您正在读取文件。这有点多余,但更重要的是while,您要通过管道传输到,这会创建一个额外的子外壳,这意味着您无法修改封闭范围内的任何变量。最好说

while IFS= read -r line ; do
    :
done < "$fname"

Next, consider your grep

接下来,考虑你的 grep

echo "$(<$rname)" | grep "$line"

Again you're reading the file and echoing it to grep. But, grepcan read files directly.

您再次读取文件并将其回显到 grep。但是,grep可以直接读取文件。

grep "$line" "$rname"

Afterwards you echo the return code and check its value in an ifstatement, which is a classic useless construct.

之后你回显返回码并在if语句中检查它的值,这是一个经典的无用构造

result=$( grep "$line" "$rname" ; echo $?)

Instead you can just pass grepdirectly to if, which will test its return code.

相反,您可以直接传递grepif,这将测试其返回代码。

if grep -q "$line" "$rname" ; then
    sed  "/$line/d" "$fname" > newkas
fi

Note here that I have quoted $fname, which is important if it might ever contain a space. I have also added -qto grep, which suppresses its output.

请注意,我在这里引用了$fname,如果它可能包含空格,这很重要。我还添加了-qto grep,这会抑制其输出。

There's now no need to suppress error messages from the ifstatement, here, because we don't have to worry about $resultcontaining an unusual value or grepnot returning properly.

现在无需if在此处抑制语句中的错误消息,因为我们不必担心$result包含异常值或grep无法正确返回。

The final result is this script

最终的结果是这个脚本

while IFS= read -r line ; do
    if grep -q "$line" "$rname" ; then
        sed  "/$line/d" "$fname" > newkas
    fi
done < "$fname"

Which will not work, because newkasis overwritten on every loop. This means that in the end only the last line in $fnamewas used. Instead you could say:

这将不起作用,因为newkas在每个循环中都会被覆盖。这意味着最后只使用了最后一行$fname。相反,你可以说:

cp "$fname" newkas
while IFS= read -r line ; do
    if grep -q "$line" "$rname" ; then
        sed  -i '' "/$line/d" newkas
    fi
done < "$fname"

Which, I believe, will do what you expect.

我相信,这会做你所期望的。

Part 3: But don't do that

第 3 部分:但不要那样做

But this is all tangential to solving your actual problem. It appears to me that you want to simply create a file newkaswhich contains the all the lines of $fnameexcept those that appear in $rname. This is easily done with the commutility:

但这与解决您的实际问题无关。在我看来,要简单地创建一个文件,newkas它包含的所有行$fname,除了那些出现在$rname。使用该comm实用程序可以轻松完成此操作:

comm -2 -3 <(sort "$fname") <(sort "$rname") > newkas

This also changes the sort order of the lines, which may not be good for you. If you want to do it without changing the ordering then using the method @fge suggests is best.

这也会更改行的排序顺序,这可能对您不利。如果您想在不更改顺序的情况下执行此操作,则最好使用@fge 建议的方法。

grep -F -v -x -f "$rname" "$fname"

回答by fge

If I understand your need correctly, you want a file newakswhich contains the lines in $fnamewhich are also in $rname.

如果我正确理解您的需求,您需要一个文件newaks,其中包含的行$fname也在$rname.

If this is what you want, using sed is overkill. Use fgrep:

如果这是您想要的,那么使用 sed 就有点过分了。使用fgrep

fgrep -x -f $fname $rname > newkas

Also, there are problems with your script:

此外,您的脚本存在问题:

  • you capture the output of grepin result, which means it will never be exactly 0; what you want is executing the command and simply check for $?
  • your echoes are convoluted, just do grep whatever thefilename, or while...done <thefile;
  • finally, you take the line as is from the source file: the line can potentially be a regex, which means you will try and match a regex in $rname, which may yield to unexpected results.
  • 您捕获grepin的输出result,这意味着它永远不会恰好为 0;你想要的是执行命令并简单地检查$?
  • 你的echoes 很复杂,就去做grep whatever thefilename,或者while...done <thefile
  • 最后,您从源文件中按原样获取该行:该行可能是一个正则表达式,这意味着您将尝试匹配 中的正则表达式$rname,这可能会产生意想不到的结果。

And others.

和别的。