Saring atau pipa bagian tertentu dari suatu file

14

Saya memiliki file input dengan beberapa bagian yang ditandai dengan tag awal dan akhir, misalnya:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

Saya ingin menerapkan transformasi ke file ini sehingga baris X, Y, Z difilter melalui beberapa perintah ( nl, misalnya), tetapi sisa baris melewati tidak berubah. Perhatikan bahwa nl(garis bilangan) mengakumulasi keadaan lintas garis, jadi ini bukan transformasi statis yang diterapkan pada masing-masing garis X, Y, Z. ( Sunting : ditunjukkan bahwa nldapat bekerja dalam mode yang tidak memerlukan status terakumulasi, tetapi saya hanya menggunakan nlsebagai contoh untuk menyederhanakan pertanyaan. Pada kenyataannya perintah adalah skrip khusus yang lebih kompleks. Apa yang benar-benar saya cari adalah solusi umum untuk masalah penerapan filter standar ke subbagian file input )

Outputnya akan terlihat seperti:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D

Mungkin ada beberapa bagian dalam file yang memerlukan transformasi.

Pembaruan 2 Saya awalnya tidak menentukan apa yang akan terjadi jika ada lebih banyak satu bagian, misalnya:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
 @@inline-code-start
line L
line M
line N
@@inline-code-end

Harapan saya adalah bahwa negara hanya perlu dipertahankan dalam bagian tertentu, memberikan:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D
     1 line L
     2 line M
     3 line N

tetapi, saya pikir menafsirkan masalah sebagai mengharuskan negara untuk dijaga lintas bagian adalah sah, dan berguna dalam banyak konteks.

Akhiri Pembaruan 2

Pikiran pertama saya adalah membuat mesin keadaan sederhana yang melacak bagian apa yang kita hadapi:

#!/usr/bin/bash
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
  echo $line | nl
  else
    # output
    echo $line
  fi
done

Yang saya jalankan dengan:

cat test-inline-codify | ./inline-codify

Ini tidak berfungsi karena setiap panggilan ke nlindependen, sehingga nomor baris tidak bertambah:

line A
line B
     1  line X
     1  line Y
     1  line Z
line C
line D

Upaya saya berikutnya adalah menggunakan fifo:

#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
    echo $line > myfifo
  else
    # output
    echo $line
  fi
done
rm myfifo

Ini memberikan output yang benar, tetapi dalam urutan yang salah:

line A
line B
line C
line D
     1  line 1
     2  line 2
     3  line 3

Mungkin ada beberapa caching yang terjadi.

Apakah saya salah tentang semua ini? Ini sepertinya masalah yang cukup umum. Saya merasa harus ada saluran pipa sederhana yang akan menyelesaikan masalah ini.

James Scriven
sumber
nltidak harus mengakumulasi negara . Lihat nl -ddan periksa man/ infohalaman untuk informasi tentang nl's bagian pembatas .
mikeserv
nl hanyalah sebuah contoh. Dalam kasus saya, saya menjalankan skrip khusus alih-alih nl.
James Scriven
Dalam hal ini, harap jelaskan apa yang sedang dilakukan skrip Anda.
terdon
Saya mengklarifikasi dalam pertanyaan yang hanya saya gunakan nlsebagai filter contoh. Saya pikir itu akan menyederhanakan pertanyaan dengan mengoleskan rincian tentang apa sebenarnya yang dilakukan filter, tapi saya mungkin hanya menyebabkan lebih banyak kebingungan. Bahkan, saya memfilter subbagian melalui stabilo kode, untuk generator blog statis yang dikembangkan sendiri. Saat ini saya menggunakan gnu source-highlight, tetapi itu mungkin berubah, dan saya mungkin menambahkan lebih banyak filter, seperti formatter juga.
James Scriven

Jawaban:

7

Saya setuju dengan Anda - mungkin ini masalah umum. Namun, beberapa utilitas umum memiliki beberapa fasilitas untuk menanganinya.


nl

nl, misalnya, memisahkan input menjadi halaman logis seperti -ddihilangkan oleh pembatas bagian dua karakter . Tiga kemunculan pada satu garis saja mengindikasikan awal dari sebuah heading , dua body dan satu footer . Ini menggantikan semua yang ditemukan dalam input dengan garis kosong dalam output - yang merupakan satu-satunya baris kosong yang pernah dicetak

Saya mengubah contoh Anda untuk memasukkan bagian lain dan memasukkannya ke dalam ./infile. Jadi sepertinya ini:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@start
line M
line N
line O
@@end

Kemudian saya menjalankan yang berikut:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end$/@@/'  <infile |
nl -d@@ -ha -bn -w1

nldapat dikatakan mengakumulasi keadaan di seluruh halaman logis, tetapi tidak secara default. Alih-alih itu akan memberi nomor baris inputnya sesuai dengan gaya , dan dengan bagian . Jadi -haberarti nomor semua baris tajuk dan -bnberarti tidak ada garis tubuh - seperti yang dimulai dalam keadaan tubuh .

Sampai aku belajar aku ini digunakan untuk menggunakan nluntuk masukan apapun, tapi setelah menyadari bahwa nlkeluaran kekuatan mendistorsi menurut default -delimiter \:saya belajar untuk lebih berhati-hati dengan itu dan mulai menggunakan grep -nF ''untuk input belum teruji sebagai gantinya. Tapi pelajaran lain yang dipelajari hari itu adalah yang nlbisa sangat berguna diterapkan dalam hal lain - seperti ini - jika Anda hanya memodifikasi inputnya hanya sedikit - seperti yang saya lakukan dengan di sedatas.

KELUARAN

  line A
  line B

1       line X
2       line Y
3       line Z

  line C
  line D

1       line M
2       line N
3       line O

Inilah beberapa tentang nl- apakah Anda memperhatikan di atas bagaimana semua garis tetapi yang bernomor dimulai dengan spasi? Ketika nlangka baris itu menyisipkan sejumlah karakter ke dalam kepala masing-masing. Untuk garis-garis itu tidak bernomor - bahkan kosong - selalu cocok dengan indent dengan memasukkan ( -wjumlah -sidth + eparator len) * spasi di kepala baris yang tidak bernomor. Ini memungkinkan Anda mereproduksi konten yang tidak bernomor persis dengan membandingkannya dengan konten bernomor - dan dengan sedikit usaha. Ketika Anda mempertimbangkan bahwa nlakan membagi inputnya menjadi bagian-bagian logis untuk Anda, dan bahwa Anda dapat menyisipkan -string sewenang-wenang di kepala setiap baris yang diberi nomor, maka itu akan cukup mudah untuk menangani outputnya:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end/@@/; t
     s/^\(@@\)\{1,3\}$/& /' <infile |
nl -d@@ -ha -bn -s' do something with the next line!
'

Cetakan di atas ...

                                        line A
                                        line B

 1 do something with the next line!
line X
 2 do something with the next line!
line Y
 3 do something with the next line!
line Z

                                        line C
                                        line D

 1 do something with the next line!
line M
 2 do something with the next line!
line N
 3 do something with the next line!
line O

GNU sed

Jika nlbukan aplikasi target Anda, maka GNU seddapat melakukan execute perintah shell sewenang-wenang untuk Anda tergantung pada pertandingan.

sed '/^@@.*start$/!b
     s//nl <<\\@@/;:l;N
     s/\(\n@@\)[^\n]*end$/\1/
Tl;e'  <infile

Di atas sedmengumpulkan input dalam ruang pola hingga cukup untuk berhasil melewati subtitusi Test dan berhenti bpeternakan kembali ke :label. Ketika itu terjadi, itu executes nldengan input diwakili sebagai <<dokumen di sini untuk semua sisa-ruang pola.

Alur kerjanya seperti ini:

  1. /^@@.*start$/!b
    • jika ^seluruh baris $tidak !tidak /cocok /dengan pola di atas, maka branched dari script dan autoprinted - sehingga dari titik ini kita hanya bekerja dengan serangkaian garis yang dimulai dengan pola.
  2. s//nl <<\\@@/
    • s//bidang kosong /berarti alamat terakhir yang seddicoba cocok - jadi perintah ini menggantikan seluruh @@.*startbaris sebagai nl <<\\@@gantinya.
  3. :l;N
    • The :perintah mendefinisikan label cabang - di sini saya menetapkan satu nama :label. The Nperintah ext menambahkan baris berikutnya dari input ke ruang pola diikuti oleh \nkarakter ewline. Ini adalah salah satu dari hanya beberapa cara untuk mendapatkan \ngaris di sedruang pola - \nkarakter garis adalah pembatas pasti untuk sedder yang telah melakukannya beberapa saat.
  4. s/\(\n@@\)[^\n]*end$/\1/
    • ini s///ubstitution hanya dapat berhasil setelah start ditemui dan hanya pada kejadian pertama berikut sebuah akhir baris. Ini hanya akan bertindak pada ruang pola di mana garis akhir akhir \nsegera diikuti dengan @@.*endmenandai bagian paling akhir $dari ruang pola. Ketika itu bertindak, itu menggantikan seluruh string yang cocok dengan grup \1pertama , atau .\(\)\n@@
  5. Tl
    • yang Tperintah est cabang untuk label (jika disediakan) jika substitusi yang berhasil belum terjadi sejak terakhir kali line input ditarik ke luar angkasa pola (seperti yang saya lakukan w / N) . Ini berarti bahwa setiap kali \newline ditambahkan ke ruang pola yang tidak cocok dengan pembatas akhir Anda, Tperintah est gagal dan bercabang kembali ke :label, yang menghasilkan sedmenarik Ngaris ekst dan mengulang sampai berhasil.
  6. e

    • Ketika substitusi untuk pertandingan akhir berhasil dan skrip tidak bercabang kembali untuk Test gagal , sedakan execute perintah yang tampak lseperti ini:

      nl <<\\@@\nline X\nline Y\nline Z\n@@$

Anda dapat melihatnya sendiri dengan mengedit baris terakhir yang ada agar terlihat seperti Tl;l;e.

Mencetak:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
     1  line M
     2  line N
     3  line O

while ... read

Salah satu cara terakhir untuk melakukan ini, dan mungkin cara yang paling sederhana, adalah menggunakan while readloop, tetapi untuk alasan yang bagus. Shell - (terutama bashshell) - biasanya sangat buruk dalam menangani input dalam jumlah besar atau aliran stabil. Ini juga masuk akal - tugas shell adalah menangani input karakter demi karakter dan untuk memanggil perintah lain yang dapat menangani hal-hal yang lebih besar.

Tetapi yang penting tentang perannya adalah bahwa shell tidak boleh read terlalu banyak dari input - itu ditentukan untuk tidak buffer input atau output ke titik yang mengkonsumsi begitu banyak atau tidak menyampaikan cukup pada waktunya sehingga perintah yang dipanggil tidak ada lagi - ke byte. Jadi readdibuat untuk tes input yang sangat baik - untuk returninformasi tentang apakah ada input yang tersisa dan Anda harus memanggil perintah berikutnya untuk membacanya - tetapi itu biasanya bukan cara terbaik untuk pergi.

Berikut ini contoh, bagaimana seseorang dapat menggunakan read dan perintah lain untuk memproses input dalam sinkronisasi:

while   IFS= read -r line        &&
case    $line in (@@*start) :;;  (*)
        printf %s\\n "$line"
        sed -un "/^@@.*start$/q;p";;
esac;do sed -un "/^@@.*end$/q;=;p" |
        paste -d: - -
done    <infile

Hal pertama yang terjadi untuk setiap iterasi adalah readmenarik garis. Jika berhasil, ini berarti loop belum menekan EOF dan karenanya dalam casecocok dengan pembatas mulai , doblok segera dieksekusi. Lain, printfcetak $lineitu readdan seddipanggil.

sedakan pmematahkan setiap baris sampai bertemu dengan penanda awal - ketika ia qmenggunakan input sepenuhnya. The -uberalih nbuffered diperlukan untuk GNU sedkarena bisa buffer agak rakus sebaliknya, tetapi - sesuai dengan spec - lain POSIX seds harus bekerja tanpa pertimbangan khusus - asalkan <infileadalah file biasa.

Ketika sed quits pertama , shell mengeksekusi doblok loop - yang memanggil orang lain sedyang mencetak setiap baris sampai bertemu dengan penanda akhir . Ini pipa outputnya ke paste, karena mencetak nomor baris masing-masing pada baris mereka sendiri. Seperti ini:

1
line M
2
line N
3
line O

pastekemudian tempelkan bersama-sama pada :karakter, dan seluruh output terlihat seperti:

line A
line B
1:line X
2:line Y
3:line Z
line C
line D
1:line M
2:line N
3:line O

Ini hanya contoh - apa pun bisa dilakukan dalam tes atau melakukan blok di sini, tetapi utilitas pertama tidak boleh mengkonsumsi terlalu banyak input.

Semua utilitas yang terlibat membaca input yang sama - dan mencetak hasilnya - masing-masing pada gilirannya sendiri. Hal semacam ini bisa sulit untuk mendapatkan menguasainya - karena utilitas yang berbeda akan buffer lebih dari yang lain - tetapi umumnya Anda bisa mengandalkan dd, headdan seduntuk melakukan hal yang benar (meskipun, untuk GNU sed, Anda memerlukan cli-switch) dan Anda harus selalu dapat mengandalkan read- karena itu, pada dasarnya, sangat lambat . Dan itulah mengapa loop di atas hanya menyebutnya satu kali per blok input.

mikeserv
sumber
Saya menguji sedcontoh kedua yang Anda berikan, dan itu berhasil, tapi saya BENAR-BENAR mengalami kesulitan mengacak sintaks. (Sed saya cukup lemah dan biasanya terbatas pada s / findthis / replacethis / g. Saya harus berusaha untuk duduk dan benar-benar mengerti sed.)
James Scriven
@ JamesScriven - Saya baru saja diedit untuk menjelaskannya dengan lebih baik. Beri tahu saya jika itu tidak membantu. Saya juga banyak mengubah perintah - ini lebih kecil, lebih masuk akal sekarang.
mikeserv
4

Satu kemungkinan adalah melakukan ini dengan editor teks vim. Itu dapat menyalurkan bagian yang sewenang-wenang melalui perintah shell.

Salah satu cara untuk melakukan ini adalah dengan nomor baris, menggunakan :4,6!nl. Perintah ex ini akan berjalan nl pada baris 4-6 inklusif, mencapai apa yang Anda inginkan pada input contoh Anda.

Cara lain yang lebih interaktif adalah dengan memilih jalur yang sesuai menggunakan mode pemilihan garis (shift-V) dan tombol panah atau pencarian, lalu gunakan :!nl. Urutan perintah lengkap untuk input contoh Anda bisa

/@@inline-code-start
jV/@@inline-code-end
k:!nl

Ini tidak terlalu cocok untuk otomatisasi (jawaban menggunakan mis. Sed lebih baik untuk itu), tetapi untuk suntingan sekali pakai, sangat berguna tidak harus menggunakan skrip shell 20-baris.

Jika Anda tidak terbiasa dengan vi (m), setidaknya Anda harus tahu bahwa setelah perubahan ini Anda dapat menyimpan file menggunakan :wq.

marcelm
sumber
Ya, vim luar biasa! Tapi saya, dalam hal ini, sedang mencari solusi skrip.
James Scriven
@ JamesScriven, siapa pun yang mengatakan vim tidak memiliki skrip yang tidak ditentukan. Pertama buat direktori proyek dan di direktori itu salin semua file startup vim dari direktori home Anda (ln -s berfungsi dengan baik kecuali untuk .vimrc yang akan kita modifikasi dan .viminfo yang mungkin dipenuhi dengan noise). Tambahkan definisi fungsi yang akan melakukan pekerjaan ke file .vimrc baru dan kemudian panggil vim sebagai HOME=$(pwd) vim -c 'call Mf()' f. Jika Anda menggunakan xargs, Anda mungkin ingin menggunakan gvim pada xserver khusus untuk mencegah kerusakan tty Anda (vnc adalah kartu video independen dan dapat dipantau).
Hildred
@hildred Hmmm ... Tidak bisakah saya menggunakan [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html ) untuk mensimulasikan klik mouse ke vim?
James Scriven
2

Perbaikan paling sederhana yang dapat saya pikirkan adalah tidak menggunakan nltetapi menghitung sendiri garis-garisnya:

#!/usr/bin/env bash
while read line
do
    if [[ $line == @@inline-code-start* ]]
    then
        active=true
    elif [[ $line == @@inline-code-end* ]]
    then
        active=false
    elif [[ $active = true ]]
    then
        ## Count the line number
        let num++;
        printf "\t%s %s\n" "$num" "$line"
    else
        # output
        printf "%s\n" "$line"
    fi
done

Anda kemudian menjalankannya di file:

$ foo.sh < file
line A
line B
    1 line X
    2 line Y
    3 line Z
line C
line D
terdon
sumber
Terima kasih terdon. Saya memperbarui pertanyaan untuk mengklarifikasi bahwa saya mencari solusi umum untuk memfilter subbagian dari input, bukan contoh spesifik dari garis penomoran. mungkin contoh perintah yang lebih baik adalah "tac" (garis terbalik)
James Scriven
2

Jika tujuan Anda adalah mengirim seluruh blok kode ke satu contoh proses maka Anda dapat mengakumulasi baris dan menunda perpipaan hingga Anda mencapai akhir blok kode:

#!/bin/bash

acc=""

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    acc=""
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    # Act on entire block of code
    echo "${acc:1}" | nl  # Chops off first leading new-line character using ${VAR:1}
  elif [[ $active = true ]]
  then
    acc=$( printf "%s\n%s" "$acc" "$line" )
  else
    # output
    echo $line
  fi
done

Ini menghasilkan yang berikut ini untuk file input yang mengulangi kasus uji tiga kali:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D

Untuk melakukan sesuatu yang lain dengan blok kode, misalnya mundur dan kemudian nomor, hanya pipa melalui sesuatu yang lain: echo -E "${acc:1}" | tac | nl. Hasil:

line A
line B
     1  line Z
     2  line Y
     3  line X
line C
line D

Atau jumlah kata echo -E "${acc:1}" | wc:

line A
line B
      3       6      21
line C
line D
Supr
sumber
2

Edit menambahkan opsi untuk mendefinisikan filter yang disediakan pengguna

#!/usr/bin/perl -s
use IPC::Open2;
our $p;
$p = "nl" unless $p;    ## default filter

$/ = "\@\@inline-code-end\n";
while(<>) { 
   chomp;
   s/\@\@inline-code-start\n(.*)/pipeit($1,$p)/se;
   print;
}

sub pipeit{my($text,$pipe)=@_;
  open2(my $R, my $W,$pipe) || die("can open2");
  local $/ = undef;
  print $W $text;
  close $W;
  return <$R>;
}

Secara default filter adalah "nl". Untuk mengubah opsi penggunaan filter "-p" dengan beberapa perintah yang disediakan pengguna:

codify -p="wc" file

atau

codify -p="sed -e 's@^@ ║ @; 1s@^@ ╓─\n@; \$s@\$@\n ╙─@'" file

Filter terakhir ini akan menampilkan:

line A
line B
 ╓─
  line X
  line Y
  line Z
 ╙─
line C
line D

Pembaruan 1 Penggunaan IPC :: Open2 memiliki masalah penskalaan: jika buffersize terlampaui, mungkin diblokir. (di mesin saya pipa memperbesar jika 64K sesuai dengan 10_000 x "garis Y").

Jika kita membutuhkan hal-hal yang lebih besar (apakah kita membutuhkan lebih dari 10.000 "garis Y"):

(1) pasang dan gunakan use Forks::Super 'open2';

(2) atau mengganti fungsi pipeit dengan:

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}
Joao
sumber
Itu keren sekali. Saya kira triknya adalah Anda tidak memproses baris per baris (dengan mendefinisikan ulang $/dan sflag), dan penggunaan eflag untuk melakukan panggilan aktual ke perintah eksternal. Saya sangat suka contoh kedua (ascii art)!
James Scriven
Namun yang saya perhatikan, adalah bahwa ini tampaknya tidak melampaui beberapa ribu garis dalam ayat ini. Saya menduga ini ada hubungannya dengan memperlakukan subbagian sebagai satu blok besar teks.
James Scriven
Terima kasih. Ya: `/ e` = eval; /s= ("." Berarti (.|\n)); $/mendefinisikan ulang pemisah register.
JJoao
@ JamesScriven, Anda benar (pipa itu menghalangi). Biarkan saya menguji apa yang sedang terjadi ...
JJoao
@ JamesScriven, lihat pembaruan saya ...
JJoao
1

Itu pekerjaan untuk awk.

#!/usr/bin/awk -f
$0 == "@@inline-code-start" {pipe = 1; next}
$0 == "@@inline-code-end" {pipe = 0; close("nl"); next}
pipe {print | "nl"}
!pipe {print}

Ketika skrip melihat penanda mulai, itu mencatat bahwa itu harus mulai disalurkan ke nl. Ketika pipevariabel benar (bukan nol), output disalurkan ke nlperintah; ketika variabel salah (tidak disetel atau nol), output dicetak langsung. Perintah pipa adalah bercabang pertama kali membangun pipa ditemui untuk setiap string perintah. Evaluasi selanjutnya dari operator pipa dengan string yang sama menggunakan kembali pipa yang ada; nilai string yang berbeda akan membuat pipa yang berbeda. The closeFungsi menutup pipa untuk string perintah yang diberikan.


Ini pada dasarnya adalah logika yang sama dengan skrip shell Anda menggunakan pipa bernama, tetapi jauh lebih mudah untuk dieja, dan logika tutup dilakukan dengan benar. Anda harus menutup pipa pada waktu yang tepat, untuk membuat nlperintah keluar, menyiram buffernya. Script Anda sebenarnya menutup pipa terlalu dini: pipa ditutup segera setelah echo $line >myfifoselesai pertama kali dijalankan. Namun nlperintah hanya melihat akhir file jika mendapat waktu sebelum skrip dijalankan echo $line >myfifo. Jika Anda memiliki volume data yang besar, atau jika Anda menambahkan sleep 1setelah menulis myfifo, Anda akan melihatnyanl hanya memproses baris pertama atau kumpulan cepat pertama, kemudian keluar karena terlihat di akhir inputnya.

Dengan menggunakan struktur Anda, Anda harus membiarkan pipa terbuka sampai Anda tidak lagi membutuhkannya. Anda perlu memiliki pengalihan output tunggal ke dalam pipa.

nl <myfifo &
exec 3>&1
while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    exec >myfifo
  elif [[ $line == @@inline-code-end* ]]
  then
    exec >&3
  else
    printf '%s\n' "$line"
  fi
done

(Saya juga mengambil kesempatan untuk menambahkan kutipan yang benar dan semacamnya - lihat Mengapa skrip shell saya tersedak di spasi putih atau karakter khusus lainnya? )

Jika Anda melakukan itu, Anda sebaiknya menggunakan pipa daripada pipa bernama.

while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    while IFS= read -r line && [[ $line != @@inline-code-end* ]] do
      printf '%s\n' "$line"
    done | nl
  else
    printf '%s\n' "$line"
  fi
done
Gilles 'SANGAT berhenti menjadi jahat'
sumber
solusi awk Anda sangat bagus! Saya pikir itu adalah solusi yang paling ringkas (namun sangat mudah dibaca). Apakah perilaku awk menggunakan kembali pipa untuk dijamin, atau bisa awk memutuskan, "hei, Anda sudah cukup menyalurkan untuk saat ini .. Saya akan menutup pipa ini dan membuka yang baru" ?. Solusi "saluran pipa" Anda juga sangat bagus. Saya awalnya mengabaikan pendekatan dengan embedded sementara loop, karena saya pikir itu mungkin agak membingungkan, tapi saya pikir apa yang Anda miliki bagus. Ada titik koma yang hilang sebelum do. (Saya tidak punya perwakilan di sini untuk mengedit sedikit.)
James Scriven
1
... Saya tidak bisa membuat solusi pipa nama Anda berfungsi. Tampaknya ada kondisi balapan, sehingga bagian yang disalurkan ke nl terkadang hilang sepenuhnya. Juga, jika ada bagian @@ inline-code-start / end kedua, selalu hilang.
James Scriven
0

OK, pertama; Saya mengerti bahwa Anda tidak mencari cara untuk memberi nomor pada baris di bagian file Anda. Karena Anda belum memberikan contoh aktual tentang apa yang mungkin menjadi filter Anda (selain nl), anggaplah demikian

tr "[[:lower:]]" "[[:upper:]]"

yaitu, konversi teks ke semua huruf besar; jadi, untuk input

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

Anda menginginkan output dari

line A
line B
LINE X
LINE Y
LINE Z
line C
line D

Inilah perkiraan pertama saya atas suatu solusi:

#!/bin/sh
> file0
> file1
active=0
nl -ba "$@" | while IFS= read -r line
do
        case "$line" in
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-start")
                active=1
                ;;
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-end")
                active=0
                ;;
            (*)
                printf "%s\n" "$line" >> file$active
        esac
done
(cat file0; tr "[[:lower:]]" "[[:upper:]]" < file1) | sort | sed 's/^[ 0-9]\{6\}        //'

di mana spasi sebelum @@string, dan di dekat akhir baris terakhir, adalah tab. Harap dicatat bahwa saya menggunakan nl untuk tujuan saya sendiri . (Tentu saja saya melakukannya untuk menyelesaikan masalah Anda masalah , tetapi tidak untuk memberi Anda output nomor baris.)

Ini memberi nomor pada garis-garis input sehingga kami dapat memecahnya di penanda bagian dan tahu cara menyusunnya kembali nanti. Bagian utama loop didasarkan pada upaya pertama Anda, dengan mempertimbangkan fakta bahwa penanda bagian memiliki nomor baris. Ini memecah input menjadi dua file: file0(tidak aktif; tidak di bagian) dan file1(aktif; di bagian). Seperti inilah tampilan mereka untuk input di atas:

file0:
     1  line A
     2  line B
     8  line C
     9  line D

file1:
     4  line X
     5  line Y
     6  line Z

Kemudian kita jalankan file1(yang merupakan gabungan dari semua baris dalam-bagian) melalui filter kapitalisasi; menggabungkannya dengan garis out-of-section tanpa filter; sortir, untuk mengembalikan mereka ke dalam urutan aslinya; dan kemudian menanggalkan nomor baris. Ini menghasilkan output yang ditunjukkan di dekat bagian atas jawaban saya.

Ini mengasumsikan bahwa filter Anda meninggalkan nomor baris sendiri. Jika tidak (mis., Jika menyisipkan atau menghapus karakter di awal baris), maka, saya percaya, pendekatan umum ini masih dapat digunakan, tetapi akan memerlukan beberapa pengkodean yang sedikit lebih rumit.

Scott
sumber
nlsudah melakukan sebagian besar pekerjaan di sana - itulah -dpilihan untuk elimiter.
mikeserv
0

Skrip shell yang menggunakan potongan sed untuk mengeluarkan garis-garis yang tidak dibatasi dan memberi makan potongan garis yang dibatasi ke dalam program filter:

#!/bin/bash

usage(){
    echo "  usage: $0 <input file>"
}

# Check input file
if [ ! -f "$1" ]; then
    usage
    exit 1
fi

# Program to use for filtering
# e.g. FILTER='tr X -'
FILTER='./filter.sh'

# Generate arrays with starting/ending line numbers of demarcators
startposs=($(grep -n '^@@inline-code-start$' "$1" | cut -d: -f1))
endposs=($(grep -n '^@@inline-code-end$' "$1" | cut -d: -f1))

nums=${#startposs[*]}
nume=${#endposs[*]}

# Verify both line number arrays have the same number of elements
if (($nums != $nume)); then
    echo "Tag mismatch"
    exit 2
fi

lastline=1
i=0
while ((i < nums)); do
    # Exclude lines with code demarcators
    sprev=$((${startposs[$i]} - 1))
    snext=$((${startposs[$i]} + 1))
    eprev=$((${endposs[$i]} - 1))

    # Don't run this bit if the first demarcator is on the first line
    if ((sprev > 1)); then
        # Output lines leading up to start demarcator
        sed -n "${lastline},${sprev} p" "$1"
    fi

    # Filter lines between demarcators
    sed -n "${snext},${eprev} p" "$1" | $FILTER

    lastline=$((${endposs[$i]} + 1))
    let i++
done

# Output lines (if any) following last demarcator
sed -n "${lastline},$ p" "$1"

Aku menulis naskah ini ke dalam sebuah file bernama detagger.sh dan menggunakannya sebagai begitu: ./detagger.sh infile.txt. Saya membuat file filter.sh terpisah untuk meniru fungsi pemfilteran dalam pertanyaan:

#!/bin/bash
awk '{ print "\t" NR " " $0}'

Tetapi operasi penyaringan dapat diubah dalam kode.

Saya mencoba mengikuti ide solusi generik dengan ini sehingga operasi seperti garis penomoran tidak memerlukan penghitungan tambahan / internal. Script melakukan beberapa pengecekan awal untuk melihat bahwa tag demarcator berpasangan dan tidak menangani tag bersarang sama sekali.

Buang air besar
sumber
-1

Terima kasih untuk semua ide bagus. Saya telah datang dengan solusi saya sendiri dengan melacak subbagian dalam file temp dan memipangnya sekaligus ke perintah eksternal saya. Ini sangat mirip dengan apa yang disarankan Supr (tetapi dengan variabel shell, bukan file temp). Juga, saya benar-benar menyukai ide menggunakan sed, tetapi sintaks untuk kasus ini tampaknya sedikit berlebihan bagi saya.

Solusi saya:

(Saya menggunakan nlhanya sebagai contoh filter)

#!/usr/bin/bash

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    tmpfile=$(mktemp)
    trap "rm -f $tmpfile" EXIT
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    <$tmpfile nl
    rm $tmpfile
  elif [[ $active = true ]]
  then
    echo $line >> $tmpfile
  else
    echo $line
  fi
done

Saya lebih suka tidak harus berurusan dengan mengelola file temp, tapi saya mengerti bahwa variabel shell dapat memiliki batas ukuran yang agak rendah, dan saya tidak tahu adanya bash construct yang akan berfungsi seperti file temp, tetapi menghilang secara otomatis ketika proses berakhir.

James Scriven
sumber
Saya pikir Anda ingin dapat “negara menumpuk di baris”, jadi, misalnya, menggunakan data uji mike ini, garis M, Ndan Oakan diberi nomor 4, 5dan 6. Ini tidak melakukan itu. Jawaban saya memang (terlepas dari kenyataan bahwa, dalam inkarnasinya saat ini, itu tidak berfungsi nlsebagai filter). Jika ini jawaban adalah memberikan Anda output yang Anda inginkan, maka apa yang Anda maksud dengan “negara menumpuk di baris”? Apakah maksud Anda bahwa Anda ingin mempertahankan status hanya melalui setiap bagian, tetapi tidak di antara bagian (lintas)? (Mengapa Anda tidak memberikan contoh multi-bagian ke dalam pertanyaan Anda?)
Scott
@Scott - gunakan nl -puntuk mendapatkan M,N,O==4,5,6.
mikeserv
Saya memperbarui pertanyaan untuk mengklarifikasi bahwa saya hanya tertarik untuk mempertahankan keadaan dalam sub-bagian, meskipun saya pikir interpretasi lain sama-sama menarik.
James Scriven