使用头和尾抓取不同的行集并保存到同一文件中


10

这是用于作业的,但我不会问具体的作业问题。

我需要使用头和尾从一个文件中抓取不同的行集。因此,像第6-11行和第19-24行一样,将它们都保存到另一个文件中。我知道我可以使用诸如

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

但是我不认为我们应该这样做。
我可以组合头命令和尾命令然后保存到文件中吗?


1
他们是否专门要求您使用headtail?如果是这样,您的解决方案几乎是您可以做到的最好的方法。如果允许您使用其他程序,sed或者awk允许使用更好的解决方案(例如,较少的流程调用)。
2015年

是的,他们要求我们使用头和尾。谢谢您的回答。
user2709291 2015年

我还可以添加一件事:>>通过将两个命令括在括号中以重定向其并置的输出,您可以解决附加输出重定向()的问题(head -11 file | tail -6; head -24 file | tail -6) > file1。这实际上归结为更好的个人喜好。
2015年

谢谢,您会做得很好。我真的很感激。
user2709291 2015年

Answers:


11

head如果您{ ... ; }使用类似以下的构造对命令进行分组,则可以使用单独的基本算术来完成此操作

{ head -n ...; head -n ...; ...; } < input_file > output_file

所有命令共享相同的输入(感谢@mikeserv)。
获取6-11行和19-24行等效于:

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

因此,基本上,您将运行:

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file

它对我不起作用。输入由第一位负责人消费
异想天开的

6

您可以使用{ … }分组构造将重定向操作符应用于复合命令。

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

您可以跳过前M行并复制下N行,而不是复制前M + N行并仅保留最后N行。这在大型文件上要快得多。注意,+N参数tail不是要跳过的行数,而是一个加号-它是要打印的第一行的行,编号从1开始。

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

无论哪种方式,输出文件仅打开一次,但对于要提取的每个摘录,遍历输入文件一次。如何分组输入?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

通常,这不起作用。(至少在输入为常规文件时,它可能在某些系统上有效。)为什么?由于输入缓冲。包括在内的大多数程序tail都不会逐字节读取输入内容,而是一次读取几千字节,因为它更快。因此,tail读取了几千字节,在开始处略过了一点,再向传递了一点head,然后停止了—但是读取的内容将被读取,并且对下一个命令不可用。

另一种方法是使用head管道传递/dev/null来跳过行。

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

同样,由于缓冲,不能保证此方法有效。head当输入来自常规文件时,它恰好可以与GNU coreutils(在非嵌入式Linux系统上找到的命令)一起使用。这是因为,一旦实现,head便将文件位置设置为未输出的第一个字节。如果输入是管道,则此方法不起作用。

从文件打印几行行的更简单方法是调用更通用的工具,如sedawk。(这可能会慢一些,但仅对于非常大的文件才有意义。)

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1

2
它不是偶然的,它是标准的指定行为-尽管可以肯定,正如您所说,管道不是共享输入的可靠输入源。实用程序描述默认值当标准实用程序读取可搜索的输入文件并在到达文件结尾之前无错误终止时,该实用程序将确保打开的文件描述中的文件偏移量恰好位于经过处理的最后一个字节之后实用程序。
mikeserv

2

我知道您说过您需要使用头部和尾部,但是sed绝对是此处工作的简单工具。

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

您甚至可以使用其他进程在字符串中构建块,然后通过sed运行它。

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n取消输出,然后指定要打印的范围p,范围的第一个和最后一个数字用逗号分隔。

话虽如此,您可以按照@don_crissti的建议进行命令分组,也可以遍历文件几次,每次遍历头/尾都要抓取几行。

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

文件中的行越多,块数越多,sed的效率就越高。


2

有了sed你可以做:

sed '24q;1,5d;12,18d' <infile >outfile

...可能有一个更有效的解决方案head。Don已经演示了如何很好地工作,但是我也一直在研究它。您可能需要处理以下特定情况:

for   n in 5 6 7 6
do    head -n"$n" >&"$((1+n%2))"
done  <infile >outfile 2>/dev/null

...这将调用head4次写入outfile或,/dev/null具体取决于该迭代的值$n是偶数还是奇数。

对于更一般的情况,我将我已经拥有的其他一些东西拼凑起来:

somehead()( 
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
    set -e -- "${1#-}" "$@"                             #-e for arg validation
    r=; cd -- "${TMP:-/tmp}"                            #go to tmp
    dd bs=4096 of="$$$$" <&4 2>&3 &                     #dd <in >tmpfile &bg
    until [ -s "$$$$" ]; do :; done                     #wait while tmpfile empty
    exec <"$$$$" 4<&-;   rm "$$$$"                      #<tmpfile; rm tmpfile
    [ "$3${1}0" -ne "$3${2#?}0" ]          ||           #validate args - chk $1
            shift "$(((r=-${1:--1})||1))"; shift        #shift 1||2
    while [ "$(((r+=(_n=1))-1))" -ne 0 ]   &&           #while ! $rptmax &&
          IFS= read -r l                   &&           #      ! EOF     &&
          printf "%.$(($1>0?${#l}+1:0))s" "$l           #      ? printf  do
";  do    for n do [ "${n#-}" -gt 0 ]      || exit      #args all -[nums>0]
          head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
    done; done                                          #done and done
)   4<&0 3>/dev/null                                    #4<for dd 3>for head

这可以做你的事情,例如:

 seq 100 | somehead -1 -5 6 -7 6

...打印...

6
7
8
9
10
11
19
20
21
22
23
24

它期望其第一个arg是一个重复计数,前缀为a -,否则,为a -。如果提供了计数,它将重复以下args中指定的线型并重复指定次数,并在这样做后立即停止。

对于后面的每个arg,它将解释一个负整数以指示应写入/dev/null的行数,并解释一个正整数以指示应写入的行数stdout

所以在上面的例子中,它打印的前5行/dev/null,接下来的6行stdout,接下来的7行/dev/null和接下来的6行stdout。到达其最后一个args并完全循环进行-1重复计数后,它退出。如果是第一个参数,-2它将再重复一次该过程,或者-尽可能重复一次。

对于每个arg循环,while循环都要处理一次。在每个循环的顶部,从第一行开始stdin将其读入shell变量$l。这是必要的,因为while head </dev/null; do :; done它将无限期地重复- head确实会在到达文件末尾时在返回中进行指示。因此,仅当第二个参数为正整数时,针对EOF的检查才专用于该操作,read并且printf将向该操作$l加上一个换行符stdout

read检查使该循环有些复杂,因为在调用另一个循环之后立即进行了循环- for循环遍历args,2-$#$n其父while循环的每次迭代中所示。这意味着对于每次迭代,第一个arg必须从命令行上指定的值减1,但是所有其他arg都应保留其原始值,因此$_n将从每个arg 中减去标记var 的值,但仅保留一个第一个arg的值大于0。

这构成了函数的主循环,但大部分代码位于顶部,旨在使函数甚至可以干净地缓冲管道作为输入。通过首先调用后台dd将其复制到tmpfile上,以4k的块大小进行输出,可以实现此目的。然后,该函数设置一个保持循环-即使在一个完整的周期内也几乎永远不会完成-只是为了确保dd在该函数随后用链接到tmpfile的文件描述符替换其stdin之前至少对该文件进行了一次写入之后立即取消链接rm。这使函数能够可靠地处理流,而无需陷阱或进行其他清理-一旦函数释放它在fd上的声明,则tmpfile将不再存在,因为其唯一的命名文件系统链接已被删除。


0

使用如下的bash函数:

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

在这种情况下,这有点过头了,但是如果您的过滤器变大,它可能会带来好处。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.