grep -v:如何仅排除匹配的前(或后)N 行?

grep -v:如何仅排除匹配的前(或后)N 行?

有时,表格数据中会有一些非常烦人的行,例如

column name | other column name
-------------------------------

我通常更喜欢通过合理唯一的字符串来删除不应该存在的垃圾行grep -v,但这种方法的问题是,如果合理唯一的字符串意外出现在数据中,那就是一个严重的问题。

有没有办法限制可以删除的行数grep -v(比如 1)?对于奖励积分,有没有一种方法可以在不使用 的情况下计算从末尾开始的行数<some command> | tac | grep -v <some stuff> | tac

答案1

您可以awk忽略第一个n匹配的行(例如,假设您只想从文件中删除第一个和第二个匹配项):

n=2
awk -v c=$n '/PATTERN/ && i++ < c {next};1' infile

忽略最后一个n匹配的行:

awk -v c=${lasttoprint} '!(/PATTERN/ && NR > c)' infile

其中是文件中最后一个匹配${lasttoprint}的第 th+1 行号。n有多种方法可以获取该行号。 (例如,通过sed/等工具仅打印每个匹配的行号awk,然后tail | head提取它)...这是一种方法gnu awk

n=2
lasttoprint=$(gawk -v c=$((n+1)) '/PATTERN/{x[NR]};
END{asorti(x,z,"@ind_num_desc");{print z[c]}}' infile)

答案2

sed提供了一个更简单的方法:

... |  sed '/some stuff/ {N; s/^.*\n//; :p; N; $q; bp}' | ...

这样您就可以删除第一个出现的位置。

如果你想要更多:

sed '1 {h; s/.*/iiii/; x}; /some stuff/ {x; s/^i//; x; td; b; :d; d}'

,其中 count ofi是出现次数(一次或多次,而不是零)。

多行解释

sed '1 {
    # Save first line in hold buffer, put `i`s to main buffer, swap buffers
    h
    s/^.*$/iiii/
    x
}

# For regexp what we finding
/some stuff/ {
    # Remove one `i` from hold buffer
    x
    s/i//
    x
    # If successful, there was `i`. Jump to `:d`, delete line
    td
    # If not, process next line (print others).
    b
    :d
    d
}'

此外

也许,这个变体会工作得更快,因为它会读取所有其余行并一次性打印它们

sed '1 {h; s/.*/ii/; x}; /a/ {x; s/i//; x; td; :print_all; N; $q; bprint_all; :d; d}'

结果

您可以将此代码放入您的外壳.bashrc(或外壳的配置中,如果是其他外壳):

dtrash() {
    if [ $# -eq 0 ]
    then
        cat
    elif [ $# -eq 1 ]
    then
        sed "/$1/ {N; s/^.*\n//; :p; N; \$q; bp}"
    else
        count=""
        for i in $(seq $1)
        do
            count="${count}i"
        done
        sed "1 {h; s/.*/$count/; x}; /$2/ {x; s/i//; x; td; :print_all; N; \$q; bprint_all; :d; d}"

    fi
}

并以这种方式使用它:

# Remove first occurrence
cat file | dtrash 'stuff' 
# Remove four occurrences
cat file | dtrash 4 'stuff'
# Don't modify
cat file | dtrash

答案3

也许可以通过使用更准确的 grep 命令来减少过滤数据的机会。例如:

grep -v -F -x 'str1'

对于以下线路完全地str1。或者可能:

grep -v '^str1.*str2$'

对于以“str1”开头并以“str2”结尾的行。

答案4

另一种可能的解决方案是使用 bash 自己的实用程序:

count=1
found=0
cat execute-commons-fileupload.sh | while read line
do 
   if [[ $line == *"myPattern"* ]]
   then 
      if [ $found -eq $count ]
      then 
         echo "$line"
      else 
         found=$(($found+1))
      fi
   else 
     echo "$line"
   fi
done

通过设置计数,您可以更改要删除的模式的出现次数。

对我个人来说,这似乎更容易扩展,因为您可以轻松地在语句中添加其他条件if(但这可能是由于我对 sed 的了解有限所致)。

相关内容