Menggunakan head dan tail untuk mengambil set garis yang berbeda dan menyimpan ke dalam file yang sama

10

Jadi ini untuk pekerjaan rumah, tapi saya tidak akan menanyakan pertanyaan pekerjaan rumah tertentu.

Saya perlu menggunakan kepala dan ekor untuk mengambil set garis yang berbeda dari satu file. Jadi seperti baris 6-11 dan baris 19-24 dan simpan keduanya ke file lain. Saya tahu saya bisa melakukan ini menggunakan append seperti

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

Tapi saya pikir kita tidak seharusnya begitu.
Apakah ada cara khusus saya bisa menggabungkan perintah kepala dan ekor dan kemudian menyimpan ke file?

pengguna2709291
sumber
1
Apakah mereka secara khusus meminta Anda untuk menggunakan headdan tail? Jika demikian, solusi Anda adalah yang terbaik yang dapat Anda lakukan. Jika Anda diizinkan menggunakan program lain, sedatau awkmungkin mengizinkan solusi yang lebih bagus (mis. Dengan lebih sedikit pemanggilan proses).
n.st
Ya, mereka meminta kita untuk menggunakan kepala dan ekor. Terima kasih atas jawaban Anda.
user2709291
Satu hal lagi yang saya dapat menambahkan: Anda bisa mendapatkan sekitar output redirection menambahkan ( >>) dengan melampirkan dua perintah dalam kurung untuk mengarahkan output bersambung mereka: (head -11 file | tail -6; head -24 file | tail -6) > file1. Itu benar-benar turun ke preferensi pribadi yang lebih baik.
n.st
Terima kasih yang akan bekerja dengan sangat baik. Saya sangat menghargai itu.
user2709291

Jawaban:

11

Anda dapat melakukannya dengan headaritmatika sendiri dan dasar, jika Anda mengelompokkan perintah dengan { ... ; }menggunakan konstruk like

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

di mana semua perintah berbagi input yang sama (terima kasih @mikeserv ).
Mendapatkan garis 6-11 dan garis 19-24 setara dengan:

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)

Jadi, pada dasarnya, Anda akan menjalankan:

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

Anda bisa menggunakan { … }konstruk pengelompokan untuk menerapkan operator pengalihan ke perintah gabungan.

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

Alih-alih menduplikasi baris M + N pertama dan hanya mempertahankan N terakhir, Anda dapat melewati baris M pertama dan menduplikasi N. berikutnya. Ini lebih cepat terukur pada file besar . Berhati-hatilah bahwa +Nargumen dari tailbukan jumlah baris untuk dilewati, tetapi satu ditambah itu - itu adalah jumlah baris pertama yang akan dicetak dengan garis bernomor mulai dari 1.

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

Either way, file output hanya dibuka sekali, tetapi file input dilewati sekali untuk setiap potongan untuk diekstraksi. Bagaimana dengan pengelompokan input?

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

Secara umum, ini tidak berhasil. (Mungkin bekerja pada beberapa sistem, setidaknya ketika inputnya adalah file biasa.) Mengapa? Karena input buffering . Sebagian besar program, termasuk tail, tidak membaca input byte demi byte, tetapi beberapa kilobyte pada satu waktu, karena lebih cepat. Jadi tailmembaca beberapa kilobyte, melompati sedikit di awal, melewati sedikit lebih ke head, dan berhenti - tetapi apa yang dibaca dibaca, dan tidak tersedia untuk perintah berikutnya.

Pendekatan lain adalah menggunakan headpiped /dev/nulluntuk melewati garis.

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

Sekali lagi, ini tidak dijamin berfungsi, karena buffering. Itu terjadi untuk bekerja dengan headperintah dari GNU coreutils (yang ditemukan pada sistem Linux yang tidak tertanam), ketika input dari file biasa. Itu karena setelah implementasi ini headtelah membaca apa yang diinginkannya, ia menetapkan posisi file ke byte pertama yang tidak di-output. Ini tidak berfungsi jika inputnya adalah pipa.

Cara yang lebih sederhana untuk mencetak beberapa urutan garis dari suatu file adalah dengan memanggil alat yang lebih umum seperti sed atau awk . (Ini bisa lebih lambat, tetapi hanya penting untuk file yang sangat besar.)

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
Gilles 'SANGAT berhenti menjadi jahat'
sumber
2
Itu tidak berhasil, itu adalah perilaku standar, ditentukan - meskipun tentu saja, seperti yang Anda katakan, pipa bukan sumber input yang dapat diandalkan untuk input bersama. KETERANGAN DESKRIPSI UTILITAS : Ketika utilitas standar membaca file input yang dapat dicari dan berakhir tanpa kesalahan sebelum mencapai akhir file, utilitas akan memastikan bahwa offset file dalam deskripsi file terbuka diposisikan dengan baik hanya melewati byte terakhir yang diproses oleh utilitas.
mikeserv
2

Saya tahu Anda mengatakan bahwa Anda perlu menggunakan kepala dan ekor, tetapi sed jelas merupakan alat yang lebih sederhana untuk pekerjaan di sini.

$ 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

Anda bahkan dapat membangun blok dalam string dengan beberapa proses lain dan menjalankannya melalui sed.

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

-n meniadakan output, maka Anda menentukan rentang untuk dicetak dengan p, dengan nomor pertama dan terakhir dari rentang dipisahkan oleh koma.

Yang sedang berkata, Anda bisa melakukan pengelompokan perintah yang disarankan @don_crissti, atau loop melalui file beberapa kali dengan kepala / ekor meraih sepotong garis setiap kali Anda melewati.

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

Semakin banyak baris dalam file dan semakin banyak blok yang Anda miliki, semakin efisien pula caranya.

Nama palsu
sumber
2

Dengan sedAnda mungkin melakukannya:

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

... Mungkin solusi yang lebih efisien dapat diperoleh head. Don sudah mendemonstrasikan bagaimana itu bisa bekerja dengan sangat baik, tetapi saya juga bermain-main dengannya. Sesuatu yang mungkin Anda lakukan untuk menangani kasus khusus ini:

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

... yang akan memanggil head4 kali penulisan untuk outfileatau /dev/nulltergantung pada apakah nilai iterasi untuk $nangka genap atau ganjil.

Untuk kasus-kasus yang lebih umum, saya menggabungkan ini dari beberapa hal lain yang sudah saya miliki:

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

Ini dapat melakukan hal Anda seperti:

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

... yang mencetak ...

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

Ia mengharapkan arg pertama menjadi hitungan berulang yang diawali dengan -, atau, gagal itu, hanya a -. Jika hitungan diberikan, ia akan mengulangi pola garis yang diberikan dalam argumen berikut sebanyak yang ditentukan dan berhenti segera setelah itu dilakukan.

Untuk setiap argumen yang mengikutinya akan menafsirkan bilangan bulat negatif untuk menunjukkan jumlah baris yang harus ditulis /dev/nulldan bilangan bulat positif untuk menunjukkan jumlah baris yang harus ditulis stdout.

Jadi pada contoh di atas ia mencetak 5 baris pertama /dev/null, 6 berikutnya stdout, 7 berikutnya /dev/nulllagi dan 6 berikutnya sekali lagi stdout. Setelah mencapai argumen terakhir dan sepenuhnya berputar melalui -1hitungan ulang, ia kemudian berhenti. Jika arg pertama adalah -2itu akan mengulangi proses sekali lagi, atau jika -selama itu bisa.

Untuk setiap siklus arg, whileloop diproses sekali melalui. Di bagian atas setiap loop, baris pertama dari stdindibaca ke variabel shell $l. Hal ini diperlukan karena while head </dev/null; do :; doneakan mengulangi tanpa batas - headtidak menunjukkan pengembalian ketika telah mencapai akhir file. Jadi pemeriksaan terhadap EOF didedikasikan untuk readdan printfakan menulis $lplus baris baru stdouthanya jika argumen kedua adalah bilangan bulat positif.

The readcek mempersulit loop kecil karena segera setelah loop lain disebut - sebuah forlingkaran yang iterates atas args 2-$#yang diwakili di $nsetiap iterasi dari induknya whilelingkaran. Ini berarti bahwa untuk setiap iterasi, arg pertama harus dikurangi dengan satu dari nilai yang ditentukan pada baris perintah, tetapi semua yang lain harus mempertahankan nilai aslinya, sehingga nilai $_nmarker var dikurangkan dari masing-masing, tetapi hanya pernah memegang nilai lebih besar dari 0 untuk arg pertama.

Itu merupakan loop utama dari fungsi, tetapi sebagian besar kode berada di bagian atas dan dimaksudkan untuk memungkinkan fungsi untuk buffer bahkan pipa sebagai input. Ini bekerja dengan terlebih dahulu memanggil latar belakang dduntuk menyalinnya ke tmpfile pada output di bloksi 4k sepotong. Fungsi ini kemudian mengatur loop terus - yang seharusnya hampir tidak pernah menyelesaikan bahkan satu siklus penuh - hanya untuk memastikan bahwa ddtelah membuat setidaknya satu tulis ke file sebelum fungsi kemudian mengganti stdin dengan deskriptor file yang terhubung ke tmpfile dan setelah itu segera putuskan tautan file denganrm. Hal ini memungkinkan fungsi untuk memproses aliran secara andal tanpa memerlukan jebakan atau sebaliknya untuk pembersihan - segera setelah fungsi melepaskannya mengklaim pada fd tmpfile akan tidak ada lagi karena satu-satunya tautan filesystem yang dinamai telah dihapus.

mikeserv
sumber
0

Gunakan fungsi bash seperti ini:

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

Ini sedikit berlebihan dalam hal ini, tetapi jika filter Anda tumbuh lebih besar itu bisa menjadi anugerah.

mkalkov
sumber