Sử dụng đầu và đuôi để lấy các bộ dòng khác nhau và lưu vào cùng một tệp


10

Vì vậy, đây là cho bài tập về nhà, nhưng tôi sẽ không hỏi câu hỏi bài tập về nhà cụ thể.

Tôi cần sử dụng đầu và đuôi để lấy các bộ dòng khác nhau từ một tệp. Vì vậy, giống như dòng 6-11 và dòng 19-24 và lưu cả hai vào một tệp khác. Tôi biết tôi có thể làm điều này bằng cách sử dụng phụ lục như

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

Nhưng tôi không nghĩ chúng ta phải làm thế.
Có cách nào cụ thể để tôi có thể kết hợp các lệnh head và tail và sau đó lưu vào tập tin không?


1
Có phải họ đặc biệt yêu cầu bạn sử dụng headtail? Nếu vậy, giải pháp của bạn là khá nhiều tốt nhất bạn có thể làm. Nếu bạn được phép sử dụng các chương trình khác, sedhoặc awkcó thể cho phép các giải pháp đẹp hơn (nghĩa là có ít yêu cầu xử lý hơn).
n.st

Vâng, họ đang yêu cầu chúng tôi sử dụng đầu và đuôi. Cảm ơn bạn vì câu trả lời.
dùng2709291

Một điều nữa tôi có thể thêm: Bạn có thể đi xung quanh chuyển hướng đầu ra bổ sung ( >>) bằng cách đặt hai lệnh trong ngoặc đơn để chuyển hướng đầu ra được nối của chúng : (head -11 file | tail -6; head -24 file | tail -6) > file1. Nó thực sự đi xuống sở thích cá nhân đẹp hơn.
n.st

Cảm ơn bạn sẽ làm việc rất tốt. Tôi rất trân trọng điều này.
dùng2709291

Câu trả lời:


11

Bạn có thể làm điều đó với headsố học đơn lẻ và cơ bản, nếu bạn nhóm các lệnh bằng { ... ; }cách sử dụng một cấu trúc như

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

trong đó tất cả các lệnh chia sẻ cùng một đầu vào (cảm ơn @mikeerv ).
Bắt các dòng 6-11 và các dòng 19-24 tương đương với:

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)

Vì vậy, về cơ bản, bạn sẽ chạy:

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

6

Bạn có thể sử dụng { … }cấu trúc nhóm để áp dụng toán tử chuyển hướng cho lệnh ghép.

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

Thay vì sao chép các dòng M + N đầu tiên và chỉ giữ lại N cuối cùng, bạn có thể bỏ qua các dòng M đầu tiên và nhân đôi dòng tiếp theo N. Điều này nhanh hơn đáng kể trên các tệp lớn . Xin lưu ý rằng +Nđối số tailkhông phải là số dòng cần bỏ qua, nhưng một điểm cộng - đó là số dòng đầu tiên được in với các dòng được đánh số từ 1.

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

Dù bằng cách nào, tệp đầu ra chỉ được mở một lần, nhưng tệp đầu vào được duyệt một lần cho mỗi đoạn trích để giải nén. Làm thế nào về việc nhóm các đầu vào?

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

Nói chung, điều này không làm việc. (Nó có thể hoạt động trên một số hệ thống, ít nhất là khi đầu vào là một tệp thông thường.) Tại sao? Vì bộ đệm đầu vào . Hầu hết các chương trình, bao gồm tail, không đọc byte đầu vào của chúng theo byte, nhưng một vài kilobyte mỗi lần, vì nó nhanh hơn. Vì vậy, tailđọc một vài kilobyte, bỏ qua một chút ở đầu, chuyển thêm một chút đến headvà dừng lại - nhưng những gì được đọc là đọc và không có sẵn cho lệnh tiếp theo.

Một cách tiếp cận khác là sử dụng headđường ống /dev/nullđể bỏ qua các dòng.

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

Một lần nữa, điều này không được đảm bảo để làm việc, do bộ đệm. Nó tình cờ hoạt động với headlệnh từ GNU coreutils (lệnh được tìm thấy trên các hệ thống Linux không nhúng), khi đầu vào là từ một tệp thông thường. Đó là bởi vì một khi việc triển khai headnày đã đọc những gì nó muốn, nó sẽ đặt vị trí tệp thành byte đầu tiên mà nó không xuất ra. Điều này không hoạt động nếu đầu vào là một đường ống.

Một cách đơn giản hơn để in một vài chuỗi dòng từ một tệp là gọi một công cụ tổng quát hơn như sed hoặc awk . (Điều này có thể chậm hơn, nhưng nó chỉ quan trọng đối với các tệp cực lớn.)

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
Nó không xảy ra để làm việc, đó là tiêu chuẩn, hành vi được chỉ định - mặc dù chắc chắn, như bạn nói, một đường ống không phải là nguồn đầu vào đáng tin cậy cho đầu vào được chia sẻ. THIẾT BỊ MÔ TẢ TIỆN ÍCH : Khi một tiện ích tiêu chuẩn đọc tệp đầu vào có thể tìm kiếm và chấm dứt mà không có lỗi trước khi đến cuối tệp, tiện ích sẽ đảm bảo rằng phần bù tệp trong mô tả tệp mở được đặt đúng vị trí chỉ qua byte cuối cùng được xử lý bởi các tiện ích.
mikeerv

2

Tôi biết bạn nói rằng bạn cần sử dụng đầu và đuôi, nhưng sed chắc chắn là công cụ đơn giản hơn cho công việc ở đây.

$ 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

Bạn thậm chí có thể xây dựng các khối trong một chuỗi với một số quy trình khác và chạy nó thông qua sed.

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

-n phủ định đầu ra, sau đó bạn chỉ định các phạm vi sẽ in bằng p, với số đầu tiên và cuối cùng của phạm vi được phân tách bằng dấu phẩy.

Điều đó đang được nói, bạn có thể thực hiện lệnh nhóm mà @don_crissti đã đề xuất hoặc lặp qua tệp một vài lần với đầu / đuôi nắm lấy một dòng các dòng mỗi khi bạn đi qua.

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

Càng nhiều dòng trong một tệp và bạn càng có nhiều khối, sed sẽ càng hiệu quả hơn.


2

Với sedbạn có thể làm:

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

... Có thể có một giải pháp hiệu quả hơn có thể có head. Don đã chứng minh làm thế nào nó có thể hoạt động rất tốt, nhưng tôi cũng đã chơi xung quanh nó. Một cái gì đó bạn có thể làm để xử lý trường hợp cụ thể này:

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

... sẽ gọi head4 lần viết hoặc đến outfilehoặc /dev/nulltùy thuộc vào giá trị lặp đó cho $nsố chẵn hay số lẻ.

Đối với các trường hợp tổng quát hơn, tôi đã kết hợp điều này với nhau từ một số nội dung khác mà tôi đã có:

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

Điều này có thể làm việc của bạn như:

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

... mà in ...

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

Nó hy vọng đối số đầu tiên của nó là số đếm lặp lại có tiền tố là a -, hoặc, không thành công, chỉ là a -. Nếu một số đếm được cung cấp, nó sẽ lặp lại mô hình dòng được đưa ra trong các đối số sau đây nhiều lần như đã chỉ định và dừng lại ngay khi nó đã làm như vậy.

Đối với mỗi đối số theo sau nó sẽ diễn giải một số nguyên âm để chỉ ra một số đếm dòng nên được ghi vào /dev/nullvà một số nguyên dương để chỉ ra một số lượng dòng nên được ghi vào stdout.

Vì vậy, trong ví dụ trên, nó in 5 dòng đầu tiên, 6 dòng /dev/nulltiếp theo stdout, 7 /dev/nulldòng tiếp theo và 6 dòng tiếp theo một lần nữa stdout. Đã đạt đến điểm cuối cùng của nó và hoàn toàn đạp xe qua -1số lần lặp lại, sau đó nó bỏ cuộc. Nếu đối số đầu tiên là -2nó sẽ lặp lại quá trình một lần nữa, hoặc nếu -nó có thể.

Đối với mỗi chu kỳ arg, whilevòng lặp được xử lý một lần thông qua. Ở đầu mỗi vòng lặp, dòng đầu tiên từ stdinđược đọc vào biến shell $l. Điều này là cần thiết bởi vì while head </dev/null; do :; donenó sẽ lặp lại vô thời hạn - headkhông cho biết sự trở lại của nó khi nó đã đến cuối tập tin. Vì vậy, kiểm tra đối với EOF được dành riêng readprintfsẽ ghi $lcộng với một dòng mới stdoutchỉ khi đối số thứ hai là số nguyên dương.

Việc readkiểm tra làm phức tạp vòng lặp một chút bởi vì ngay sau khi một vòng lặp khác được gọi - một forvòng lặp lặp lại các đối số 2-$#như được biểu thị $ncho mỗi lần lặp của whilevòng lặp cha của nó . Điều này có nghĩa là với mỗi lần lặp, đối số đầu tiên phải được giảm bởi một từ giá trị được chỉ định trên dòng lệnh, nhưng tất cả các giá trị khác nên giữ lại các giá trị ban đầu của chúng, và do đó, giá trị của $_nvar đánh dấu được trừ đi từ mỗi, nhưng chỉ giữ một giá trị lớn hơn 0 cho đối số đầu tiên.

Điều đó tạo thành vòng lặp chính của chức năng, nhưng phần lớn mã nằm ở trên cùng và nhằm mục đích cho phép chức năng đệm sạch ngay cả một đường ống làm đầu vào. Điều này hoạt động bằng cách trước tiên gọi một nền tảng ddđể sao chép nó vào một tmpfile trên đầu ra với kích thước 4k một mảnh. Sau đó, hàm sẽ thiết lập một vòng giữ - gần như không bao giờ hoàn thành ngay cả một chu kỳ đầy đủ - chỉ để đảm bảo rằng ddđã thực hiện ít nhất một lần ghi vào tệp trước khi hàm thay thế stdin của nó bằng một mô tả tệp được liên kết với tmpfile và sau đó ngay lập tức hủy liên kết tệp vớirm. Điều này cho phép chức năng xử lý luồng một cách đáng tin cậy mà không yêu cầu bẫy hoặc nếu không để dọn dẹp - ngay khi chức năng giải phóng nó yêu cầu trên fd, tmpfile sẽ ngừng tồn tại vì liên kết hệ thống tệp có tên duy nhất của nó đã bị xóa.


0

Sử dụng hàm bash như thế này:

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

Đây là một chút quá mức trong trường hợp này, nhưng nếu bộ lọc của bạn phát triển lớn hơn, nó có thể trở thành một lợi ích.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.