bash 如何从文件或脚本中的管道中选择多行?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27509898/
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 12:01:32  来源:igfitidea点击:

How to select multiple lines from a file or from pipe in a script?

linuxbashsedtext-processing

提问by Brad Parks

I'd like to have a script, called lines.shthat I can pipe data to to select a series of lines.

我想要一个脚本,称为lines.sh我可以通过管道传输数据以选择一系列行。

For example, if I had the following file:

例如,如果我有以下文件:

test.txt

测试.txt

a 
b
c
d

Then I could run:

然后我可以运行:

cat test.txt | lines 2,4

and it would output

它会输出

b
d

I'm using zsh, but would prefer a bash solution if possible.

我正在使用 zsh,但如果可能的话,我更喜欢 bash 解决方案。

回答by anubhava

You can use this awk:

你可以使用这个awk:

awk -v s='2,4' 'BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b' file
two
four

Via a separate script lines.sh:

通过单独的脚本lines.sh

#!/bin/bash
awk -v s="" 'BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b' ""

Then give execute permissions:

然后赋予执行权限:

chmod +x lines.sh

And call it as:

并将其称为:

./lines.sh '2,4' 'test.txt'

回答by pfnuesel

Try sed:

尝试sed

sed -n '2p; 4p' inputFile

-ntells sedto suppress output, but for the lines 2and 4, the p(print) command is used to print these lines.

-n告诉sed抑制输出,但对于行24p(print) 命令用于打印这些行。

You can also use ranges, e.g.:

您还可以使用范围,例如:

sed -n '2,4p' inputFile

回答by gniourf_gniourf

Two pure Bash versions. Since you're looking for general and reusable solutions, you might as well put a little bit of effort in that. (Also, see last section).

两个纯 Bash 版本。既然您正在寻找通用且可重用的解决方案,那么您不妨在这方面付出一些努力。(另请参阅最后一节)。

Version 1

版本 1

This script slurps the entire stdin into an array (using mapfile, so it's rather efficient) and then prints the lines specified on its arguments. Ranges are valid, e.g.,

该脚本将整个 stdin 放入一个数组中(使用mapfile,因此效率很高),然后打印在其参数中指定的行。范围是有效的,例如,

1-4 # for lines 1, 2, 3 and 4
3-  # for everything from line 3 till the end of the file

You may separate these by spaces or commas. The lines are printed exactly in the order the arguments are given:

您可以用空格或逗号将它们分开。这些行完全按照给定参数的顺序打印:

lines 1 1,2,4,1-3,4- 1

will print line 1 twice, then line 2, then line 4, then lines 1, 2 and 3, then everything from line 4 till the end, and finally, line 1 again.

将打印第 1 行两次,然后是第 2 行,然后是第 4 行,然后是第 1、2 和 3 行,然后是从第 4 行到最后的所有内容,最后再次打印第 1 行。

Here you go:

干得好:

#!/bin/bash

lines=()

# Slurp stdin in array
mapfile -O1 -t lines

# Arguments:
IFS=', ' read -ra args <<< "$*"

for arg in "${args[@]}"; do
   if [[ $arg = +([[:digit:]]) ]]; then
      arg=$arg-$arg
   fi
   if [[ $arg =~ ([[:digit:]]+)-([[:digit:]]*) ]]; then
      ((from=10#${BASH_REMATCH[1]}))
      ((to=10#${BASH_REMATCH[2]:-$((${#lines[@]}))}))
      ((from==0)) && from=1
      ((to>=${#lines[@]})) && to=${#lines[@]}
      ((from<=to)) || printf >&2 'Argument %d-%d: lines not in increasing order' "$from" "$to"
      for((i=from;i<=to;++i)); do
         printf '%s\n' "${lines[i]}"
      done
   else
      printf >&2 "Error in argument \`%s'.\n" "$arg"
   fi
done
  • Pro: It's really cool.
  • Con: Needs to read entire stream into memory. Not suitable for infinite streams.
  • 亲:真的很酷。
  • 缺点:需要将整个流读入内存。不适合无限流。

Version 2

版本 2

This version addresses the previous problem of infinite streams. But you'll lose the ability to repeat and reorder lines.

此版本解决了之前的无限流问题。但是您将失去重复和重新排序行的能力。

Same thing, ranges are allowed:

同样的事情,范围是允许的:

lines 1 1,4-6 9-

will print lines 1, 4, 5, 6, 9 and everything till the end. If the set of lines is bounded, exits as soon as last line is read.

将打印第 1、4、5、6、9 行以及所有内容直到最后。如果行集有界,则在读取最后一行后立即退出。

#!/bin/bash

lines=()
tillend=0
maxline=0

# Process arguments
IFS=', ' read -ra args <<< "$@"

for arg in "${args[@]}"; do
   if [[ $arg = +([[:digit:]]) ]]; then
       arg=$arg-$arg
   fi
   if [[ $arg =~ ([[:digit:]]+)-([[:digit:]]*) ]]; then
      ((from=10#${BASH_REMATCH[1]}))
      ((from==0)) && from=1
      ((tillend && from>=tillend)) && continue
      if [[ -z ${BASH_REMATCH[2]} ]]; then
         tillend=$from
         continue
      fi
      ((to=10#${BASH_REMATCH[2]}))
      if ((from>to)); then
         printf >&2 "Invalid lines order: %s\n" "$arg"
         exit 1
      fi
      ((maxline<to)) && maxline=$to
      for ((i=from;i<=to;++i)); do
         lines[i]=1
      done
   else
      printf >&2 "Invalid argument \`%s'\n" "$arg"
      exit 1
   fi
done

# If nothing to read, exit
((tillend==0 && ${#lines[@]}==0)) && exit

# Now read stdin
linenb=0
while IFS= read -r line; do
   ((++linenb))
   ((tillend==0 && maxline && linenb>maxline)) && exit
   if [[ ${lines[linenb]} ]] || ((tillend && linenb>=tillend)); then
      printf '%s\n' "$line"
   fi
done
  • Pro: It's really cool and doesn't read the full stream in memory.
  • Con: Can't repeat or reorder lines as Version 1. Speed is not is it's strongest point.
  • 优点:它真的很酷,并且不会读取内存中的完整流。
  • 缺点:不能像版本 1 那样重复或重新排列行。速度不是它的最强点。

Further thoughts

进一步的想法

If you really want an awesome general script that does what Version 1 and Version 2 does, and more, you definitely should consider using another language, e.g., Perl: you'll gain a lot (in particular speed)! you'll be able to have nice options that'll do lots of much cooler stuff. It might be worth it in the long run, as you want a general and reusable script. You might even end up having a script that reads emails!

如果您真的想要一个很棒的通用脚本来执行版本 1 和版本 2 的功能,甚至更多,您绝对应该考虑使用另一种语言,例如 Perl:您将获得很多(尤其是速度)!你将能够有很好的选择,可以做很多更酷的事情。从长远来看,这可能是值得的,因为您需要一个通用且可重用的脚本。您甚至可能最终拥有一个阅读电子邮件的脚本!



Disclaimer.I haven't thoroughly checked these scripts... so beware of bugs!

免责声明。我还没有彻底检查这些脚本......所以要小心错误!

回答by midori

Quick solution for you friend. Input:

您朋友的快速解决方案。输入:

test.txt

测试.txt

a
b
c
d
e
f
g
h
i
j

test.sh

测试文件

lines (){
sed -n "$( echo "$@" | sed 's/[0-9]\+/&p;/g')"
}

cat 1.txt | lines 1 5 10

Or if you want to have your linesas script:

或者,如果您想使用linesas 脚本:

lines.sh

行.sh

IFS=',' read -a lines <<< ""; sed -n "$( echo "${lines[@]}" | sed 's/[0-9]\+/&p;/g')" ""

./lines.sh 1,5,10 test.txt

Output in both cases:

两种情况下的输出:

a
e
j

回答by ChatterOne

Well, provided that:

好吧,前提是:

  • your file is small enough
  • you don't have any semicolon (or another specific character of your choice) in the file
  • you don't mind using multiple pipes
  • 你的文件足够小
  • 您在文件中没有任何分号(或您选择的其他特定字符)
  • 你不介意使用多个管道

you could use something like:

你可以使用类似的东西:

cat test.txt |tr "\n" ";"|cut -d';' -f2,4|tr ";" "\n"

Where -f2,4 indicates the lines you want to extract

其中 -f2,4 表示要提取的行

回答by Bernardo Rufino

If this is a one-time operation and there aren't many lines to pick, you could use pickto manually select them:

如果这是一次性操作并且没有很多行可以选择,您可以使用pick手动选择它们:

cat test.txt | pick | ...

A interactive screen would open allowing you to select what you want.

将打开一个交互式屏幕,允许您选择所需的内容。

回答by buydadip

Try this :

尝试这个 :

file=
for var in "$@"  //var is all line numbers
do
sed -n "${var}p" $file
done

I created a script with 1 file parameter, and an unlimited number of parameters for line numbers. You would call it as so :

我创建了一个带有 1 个文件参数和无限数量的行号参数的脚本。你会这样称呼它:

lines txt 2 3 4...etc