Bir dosyanın belirli bölümlerini filtreleme veya yöneltme


14

Bazı bölümleri olan bir giriş dosyası var, örneğin başlangıç ​​ve bitiş etiketleri ile sınırlanmıştır:

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

Bu dosyaya, X, Y, Z satırlarının bazı komutlarla ( nlörneğin) filtrelendiği , ancak satırların geri kalanının değişmeden geçtiği şekilde bir dönüşüm uygulamak istiyorum . nl(Sayı satırlarının) satırlar boyunca durumu biriktirdiğine dikkat edin , bu nedenle X, Y, Z satırlarının her birine uygulanan statik bir dönüşüm değildir. ( Düzenleme : nlbirikmiş devlet gerektirmeyen bir modda çalışabilir işaret etti , ama ben sadece nlsoruyu basitleştirmek için örnek olarak kullanıyorum . Gerçekte komut daha karmaşık bir özel komut dosyasıdır. Gerçekten ne arıyorum for, bir giriş dosyasının alt bölümüne standart bir filtre uygulama sorununa genel bir çözümdür )

Çıktı şöyle görünmelidir:

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

Dosyada, dönüşüm gerektiren bu tür birkaç bölüm olabilir.

Güncelleme 2 Başlangıçta daha fazla bir bölüm varsa ne olması gerektiğini belirtmedim, örneğin:

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

Benim beklentim, devletin sadece belirli bir bölümde muhafaza edilmesi gerektiğidir.

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

ancak, sorunun devletin bölümler arasında tutulmasını gerektirdiği şeklinde yorumlanmasının birçok bağlamda geçerli ve yararlı olduğunu düşünüyorum.

Son Güncelleme 2

İlk düşüncem, hangi bölümde olduğumuzu takip eden basit bir durum makinesi oluşturmaktır:

#!/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

Hangi ile koşmak:

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

Her çağrı nlbağımsız olduğu için bu çalışmaz , bu nedenle hat numaraları artmaz:

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

Bir sonraki denemem bir fifo kullanmaktı:

#!/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

Bu doğru çıktıyı verir, ancak yanlış sırayla:

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

Muhtemelen bazı önbellekleme var.

Bunların hepsini yanlış mı yapıyorum? Bu oldukça genel bir sorun gibi görünüyor. Bunu çözecek basit bir boru hattı olması gerektiğini hissediyorum.


nldevlet biriktirmek zorunda değildir . Bak az nl -dsizin kontrol etmek ve man/ infohakkında bilgi için sayfaları nlbireyin bölüm sınırlayıcı .
mikeserv

nl sadece bir örnektir. Benim durumumda nl yerine özel bir komut dosyası çalıştırıyorum.
James Scriven

Bu durumda, lütfen komut dosyanızın ne yaptığını netleştirin.
terdon

Sadece nlörnek filtre olarak kullandığım soruya açıklık getirdim . Filtrenin tam olarak ne yaptığını ayrıntılarıyla aydınlatarak soruyu basitleştireceğini düşündüm, ancak muhtemelen daha fazla karışıklığa neden oldum. Aslında, alt bölümleri bir kod vurgulayıcı aracılığıyla filtreliyorum, evde üretilen bir statik blog üreticisi için. Şu anda gnu kullanıyorum source-highlight, ancak bu değişebilir ve ben de biçimlendirici gibi daha fazla filtre ekleyebilirim.
James Scriven

Yanıtlar:


7

Sana katılıyorum olur - bu muhtemelen olan genel bir sorun. Bununla birlikte, bazı yaygın yardımcı programların, onu işlemek için bazı olanakları vardır.


nl

nlörneğin, girdiyi mantıksal sayfalara-d iki karakterli bir bölüm sınırlayıcı tarafından kaldırıldığı şekilde ayırır . Bir çizgide tek başına üç tekrar, bir başlığın başlangıcını gösterir , iki gövde ve bir altbilgi . Girişte bulunan bunlardan herhangi birini çıktıdaki boş bir satırla değiştirir - yazdırdığı tek boş satırdır

Örneğinizi başka bir bölüm içerecek şekilde değiştirdim ve yerleştirdim ./infile. Yani şöyle görünüyor:

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

Sonra aşağıdakileri çalıştırdım:

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

nlmantıksal sayfalarda durumu biriktirdiği söylenebilir , ancak varsayılan olarak değildir. Bunun yerine, girdisinin çizgilerini stillere ve bölümlere göre numaralandıracaktır . Yani -ha, tüm başlık satırlarını saymak ve bir vücut durumunda başladığı için hiçbir vücut çizgisi-bn anlamına gelmez .

Bunu öğrenene kadar nlherhangi bir girdi için kullanıyordum , ancak nlbunun varsayılan -delimiter'e göre çıktıyı bozabileceğini fark ettikten sonra \:, ona daha dikkatli olmayı öğrendim ve grep -nF ''bunun yerine test edilmemiş girdi için kullanmaya başladım . Ancak o gün öğrenilen bir başka ders nl, sedyukarıdaki gibi sadece girişini biraz değiştirirseniz - bu gibi - diğer açılardan çok yararlı bir şekilde uygulanabileceğiydi .

ÇIKTI

  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

İşte biraz daha nl- numaralandırılmışlar dışındaki tüm satırların boşlukla nasıl başladığını yukarıda fark ettiniz mi? Tüm nlsayılar hatları her kafasının içine karakter belirli sayıda ekler. Bu satırlar için numaralandırılmaz - boşluklar bile - numarasız satırların başına (idth -wcount + -separator len) * boşlukları ekleyerek girintiyle her zaman eşleşir . Bu, numaralandırılmamış içeriği tam olarak numaralandırılmış içerikle karşılaştırarak ve çok az çaba sarf etmenize olanak tanır. Bunun sizin nliçin mantıksal bölümlere bölüneceğini ve -ssayıladığı her satırın başına rastgele eğilimler ekleyebileceğinizi düşündüğünüzde, çıktısını işlemek oldukça kolaylaşır:

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

Yukarıdaki baskılar ...

                                        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

Eğer nlhedef uygulaması değil, o bir GNU sedolabilir eBir maç bağlı için keyfi bir kabuk komutu xecute.

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

Yukarıdaki sed, ikame Testini başarıyla geçip babel'e geri dönmeyi durduracak kadar desen uzayında girişi toplar :l. eYaptığında , desen alanının geri kalan kısmı için burada bir belge nlolarak gösterilen girdiyle xecutes yapar <<.

İş akışı şu şekildedir:

  1. /^@@.*start$/!b
    • Bir eğer ^tüm çizgi $yok !değil /maç /yukarıdaki kalıbı, o zaman olduğu bsenaryo dışına çiftçiliği ve autoprinted - yani bu noktadan itibaren sadece desen ile başladı çizgilerin bir dizi ile çalışıyorsanız üzerinde.
  2. s//nl <<\\@@/
    • boş s//alan /, sedeşleştirilmeye çalışılan son adresin yerini alır - dolayısıyla bu komut tüm @@.*startsatırın nl <<\\@@yerine kullanılır.
  3. :l;N
    • :Komutu bir şube etiketi tanımlar - burada bir adlandırılmış set :lHabil'i. NExt komutu bir takip desen boşluğa girişinin sonraki çizgisini ekler \newline karakteri. Bu, örüntü alanında \newline almanın birkaç yolundan biridir sed- \newline karakteri, sedbunu bir süredir yapan bir der için kesin bir sınırlayıcıdır .
  4. s/\(\n@@\)[^\n]*end$/\1/
    • bu s///ikame ancak bir başlangıçla karşılaştıktan sonra ve yalnızca bir bitiş çizgisinin ilk takipinde başarılı olabilir . Sadece son \newline'ın hemen ardından desen alanının @@.*endsonunu işaretleyen bir desen alanı üzerinde hareket edecektir $. Hareket ettiğinde, eşleşen dizenin tamamını \1ilk \(grupla değiştirir \)veya \n@@.
  5. Tl
    • TBir etiket est komut dalları (eğer varsa) , başarılı bir ikame olarak bir giriş hattı paterni boşluğa çekilmiş son kez gerçekleşmediyse (w / gibi N) . Bu \n, son sınırlayıcınızla eşleşmeyen desen alanına her bir ewline eklendiğinde, Test komutu başarısız olur ve abel'e geri döner :l, bu sedda Next satırında çekme ve başarılı olana kadar döngü ile sonuçlanır.
  6. e

    • Son eşlemenin yerine koyma başarılı olduğunda ve komut dosyası başarısız bir Test için geri dönmediğinde, aşağıdakine benzer bir komutu xecute sededecektir :el

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

Buradaki son satırı düzenleyerek kendiniz görebilirsiniz Tl;l;e.

Yazdırır:

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

Bunu yapmanın son bir yolu ve belki de en basit yolu, bir while readdöngü kullanmaktır , ancak iyi bir nedenden dolayı. Kabuk - (özellikle bir bashkabuk) - girdiyi büyük miktarlarda veya sabit akışlarla işlemede tipik olarak oldukça uçsuzdur. Bu da mantıklıdır - kabuğun görevi karakterleri karaktere göre ele almak ve daha büyük şeyleri işleyebilecek diğer komutları çağırmaktır.

Ama önemlisi onun rolü hakkında kabuk olduğunu orada olmamalıdır read bunun için belirtilen - girdinin overmuch değil o kadar çok tüketir veya eksik komutlar da çağrılar bırakılır bu süre içinde yeterince röle olmadığı noktaya giriş veya çıkışı tampon - bayta. Bu yüzden readmükemmel bir giriş testi yapar - returnkalan giriş olup olmadığı hakkında bilgi ve bunu okumak için bir sonraki komutu çağırmanız gerekir - ancak aksi takdirde genellikle en iyi yol değildir.

Bununla birlikte, senkronize olarak girişi işlemek için birinin nasıl kullanılabileceğine read ve diğer komutlara bir örnek :

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

Her yinelemede gerçekleşen ilk şey readbir çizgi çeker. Başarılı olursa, döngü henüz EOF'ye çarpmadığı anlamına gelir ve bu nedenle casebir başlangıç sınırlayıcıyla eşleştiğinde doblok hemen yürütülür. Else, onu printfyazdırır ve denir.$linereadsed

sedolacak pkarşılaştığı dek her satırını Rint başlangıç o zaman - işaretleyici qtamamen girişini UITS. -uNbuffered anahtarı GNU için gerekli olan sedo açgözlülükle aksi ziyade tampon, ancak çünkü - spec göre - diğer POSIX sedlar herhangi bir özel göz olmadan çalışması gerekir - bu yüzden sürece <infilenormal bir dosyadır.

İlk sed quyduğunda, kabuk , son işaretiyle karşılaşana kadar her satırı yazdıran dobaşka bir çağıran döngü bloğunu yürütür . Bu borular etmek çıkışını , onların her satırda bir satır numaralarını yazdırır çünkü. Bunun gibi:sedpaste

1
line M
2
line N
3
line O

pastedaha sonra bunları :karakterlere yapıştırır ve tüm çıktı şuna benzer:

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

Bunlar sadece örnektir - burada testte veya bloklarda her şey yapılabilir, ancak ilk yardımcı program çok fazla girdi tüketmemelidir.

İlgili tüm yardımcı programlar aynı girdiyi okur ve sonuçlarını kendi sırayla yazdırır. Farklı programları diğerlerinden daha tampon çünkü - - Bu tür bir şey asmak için zor olabilir ancak genellikle güvenebileceğiniz dd, headve seddoğru olanı yapmak (GNU için, gerçi sed, sen cli-şalterini gerekir) ve her zaman güvenebilmelisiniz read- çünkü doğa gereği çok yavaştır . Bu yüzden yukarıdaki döngü bunu giriş bloğu başına sadece bir kez çağırır.


sedVerdiğiniz ikinci örneği test ettim ve işe yarıyor, ama gerçekten sözdizimi grokking konusunda sorun yaşıyorum. (benim sed oldukça zayıf ve genellikle s / findthis / replacethis / g ile sınırlıdır. Oturmak ve
sed'i

@JamesScriven - Daha iyi açıklamak için düzenledim. Yardımcı olmuyorsa bana bildirin. Ayrıca komutu çok değiştirdim - şimdi daha küçük, daha mantıklı parçalarda.
mikeserv

4

Bir olasılık bunu vim metin editörü ile yapmaktır. Kabuk komutları ile rastgele bölümler oluşturabilir.

Bunu yapmanın bir yolu, kullanarak satır numaralarıdır :4,6!nl. Bu ex komutu 4-6 dahil satırlarda nl çalıştırarak örnek girişinizde ne istediğinizi gerçekleştirecektir.

Daha etkileşimli bir başka yol ise, satır seçim modunu (shift-V) ve ok tuşlarını kullanarak veya arama yaparak ve ardından uygun satırları seçmektir :!nl. Örnek girişiniz için tam komut dizisi

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

Bu otomasyon için çok uygun değildir (örn. Sed kullanan cevaplar bunun için daha iyidir), ancak bir kerelik düzenlemeler için 20 satırlı kabuk komut dosyalarına başvurmak zorunda değilsiniz.

Vi (m) 'ye aşina değilseniz, en azından bu değişikliklerden sonra dosyayı kullanarak kaydedebileceğinizi bilmelisiniz. :wq .


Evet, vim harika! Ama ben bu durumda, yazılabilir bir çözüm arıyorum.
James Scriven

@JamesScriven, vim diyenlerin yeterince belirlenemediğini söyleyenler. Öncelikle bir proje dizini oluşturun ve bu dizinde vim'in tüm başlangıç ​​dosyalarını ana dizininizden kopyalayın (değiştirmek üzere olduğumuz .vimrc ve gürültüyle doldurulabilen .viminfo dışında ln -s iyi çalışır). İşi yapacak işlev tanımını yeni .vimrc dosyasına ekleyin ve sonra vim as komutunu çağırın HOME=$(pwd) vim -c 'call Mf()' f. Xargs kullanıyorsanız, tty'nizi bozmamak için özel bir xserver üzerinde gvim kullanmak isteyebilirsiniz (vnc video kartından bağımsızdır ve izlenebilir).
hildred

@hildred Hmmm ... Vim için fare tıklamalarını simüle etmek için sadece [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html ) kullanamaz mıydım ?
James Scriven

2

Düşünebildiğim en basit düzeltme nl, satırları kendiniz kullanmamak ama saymaktır:

#!/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

Sonra dosya üzerinde çalıştırın:

$ foo.sh < file
line A
line B
    1 line X
    2 line Y
    3 line Z
line C
line D

Teşekkürler terdon. Bir girdinin alt bölümünü filtrelemek için genel bir çözüm aradığımı açıklamak için soruyu güncelledim, numaralandırma hatlarının spesifik örneği. belki daha iyi bir örnek komut "tac" (ters çizgiler) olurdu
James Scriven

2

Amacınız kod bloğunun tamamını tek bir işlem örneğine göndermekse, satırları biriktirebilir ve kod bloğunun sonuna ulaşıncaya kadar boruları geciktirebilirsiniz:

#!/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

Bu, test durumunu üç kez tekrarlayan bir giriş dosyası için aşağıdakileri üretir:

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

Başka bir şey aracılığıyla, sadece boruyu o örneğin kod bloğu ile başka bir şey yapmak ardından numarayı ters ve için: echo -E "${acc:1}" | tac | nl. Sonuç:

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

Veya wordcount echo -E "${acc:1}" | wc:

line A
line B
      3       6      21
line C
line D

2

Düzenle , kullanıcı tarafından sağlanan bir filtreyi tanımlamak için bir seçenek ekledi

#!/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>;
}

Varsayılan olarak filtre "nl" dir. Filtreyi değiştirmek için kullanıcı tarafından sağlanan komutla "-p" seçeneğini kullanın:

codify -p="wc" file

veya

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

Bu son filtrenin çıktısı:

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

Güncelleme 1 IPC :: Open2 kullanımının ölçeklendirme sorunları var: arabellek boyutu aşılırsa engellenebilir. (makinemde boru, 64K 10_000 x "satır Y" ye karşılık gelirse arabelleğe alınır).

Daha büyük şeylere ihtiyacımız varsa (daha fazla 10000 "hat Y" ye ihtiyacımız var):

(1) kurulum ve kullanım use Forks::Super 'open2';

(2) veya pipeit işlevini aşağıdakilerle değiştirir:

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}

Bu gerçekten havalı. Ben hileler satır satır (yeniden $/ve sbayrağını) işleme eve dış komut için gerçek çağrı yapmak için bayrağını kullanmak değildir sanırım . Gerçekten ikinci (ascii sanatı) örneği seviyorum!
James Scriven

Yine de fark ettiğim şey, bunun alt bölümdeki birkaç bin çizginin ötesinde ölçeklenmediği. Bunun alt bölüme büyük bir metin bloğu gibi davranması gerektiğinden şüpheleniyorum.
James Scriven

Teşekkürler. Evet: `/ e` = eval; /s= (".") anlamına gelir (.|\n); $/yazmaç ayırıcıyı yeniden tanımlar.
JJoao

@ JamesScriven, haklısın (boru tıkalı). Neler olduğunu test edeyim ...
JJoao

@JamesScriven, lütfen güncellememi görün ...
JJoao

1

Bu garip bir iş.

#!/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}

Komut dosyası başlangıç ​​işaretleyicisini gördüğünde, içine girmeye başlaması gerektiğini not eder nl. Tüm pipebir değişken (sıfır olmayan) doğru, çıktı olarak yollanır nlkomut; değişken false (ayarlanmamış veya sıfır) olduğunda, çıktı doğrudan yazdırılır. Piped komutu, her komut dizesi için boru yapısıyla ilk kez karşılaşıldığında çatallanır. Aynı ip ile boru operatörünün sonraki değerlendirmeleri mevcut boruyu yeniden kullanır; farklı bir dize değeri farklı bir kanal oluşturur. closeFonksiyonu verilen komut dizesi için boruyu kapatır.


Bu aslında adlandırılmış bir boru kullanan kabuk komut dosyanızla aynı mantıktır, ancak yazılması çok daha kolaydır ve yakın mantık doğru yapılır. nlKomutun tamponlarını temizleyerek komutun çıkmasını sağlamak için boruyu doğru zamanda kapatmanız gerekir . Betiğiniz aslında boruyu çok erken kapatır: ilk echo $line >myfifoyürütmeyi bitirir bitirmez boru kapatılır . Ancak nlkomut, dosyanın bir sonraki çalıştırılmasından önce bir zaman dilimi alırsa dosyanın sonunu görür echo $line >myfifo. Büyük miktarda veriye sahipseniz veya sleep 1yazdıktan sonra eklerseniz myfifo, nlyalnızca ilk satırı veya ilk hızlı satırları işlediğini görürsünüz , o zaman çıkışının sonunu gördüğü için çıkar.

Yapınızı kullanarak boruyu artık ihtiyacınız olmayacak kadar açık tutmanız gerekir. Boruya tek bir çıkış yönlendirmesi yapmanız gerekir.

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

(- bakınız ben de alıntı ve bu tür doğru ekleme fırsatı aldı ? Boşluk veya diğer özel karakterler benim kabuk komut dosyası jikleyi yapar Neden )

Bunu yapıyorsanız, adlandırılmış bir boru yerine bir boru hattı da kullanabilirsiniz.

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

awk çözümünüz gerçekten güzel! Bence bu en özlü (ancak çok okunabilir) çözüm. Awk'un boruyu nl olarak yeniden kullanma davranışı garantili mi, yoksa awk, "hey, şimdilik yeterince boru bağladın mı ... Bu boruyu kapatıp yeni bir tane açacağım" mı? "Boru hattı" çözümünüz de gerçekten güzel. Biraz kafa karıştırıcı olabileceğini düşündüğüm için gömülü döngülerle bir yaklaşımı orjinal olarak indirdim, ama sahip olduğun şeyin harika olduğunu düşünüyorum. Önce noktalı virgül eksik do. (Burada küçük bir düzenleme yapmak için temsilcim yok.)
James Scriven

1
... adlandırılmış boru çözümünüzü çalıştıramadım. Bir yarış koşulu var gibi görünüyor, böylece nl'ye borulu bölüm bazen tamamen kayboluyor. Ayrıca, ikinci bir @@ satır içi kod başlangıç ​​/ bitiş bölümü varsa, her zaman kaybolur.
James Scriven

0

Tamam, ilk önce; Dosyanızın bölümlerindeki satırları numaralandırmak için bir yol aramayacağınızı anlıyorum . Filtrenizin ne olabileceğine (dışında nl) gerçek bir örnek vermediğiniz için, varsayalım ki

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

örneğin, metni büyük harfe dönüştürme; yani, girişi için

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

çıktı almak istiyorsun

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

İşte benim bir çözüm ilk yaklaşımım:

#!/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\}        //'

burada @@dizelerden önceki ve son satırın sonuna yakın olan boşluklar sekmelerdir. nl Kendi amaçlarım için kullandığımı lütfen unutmayın . (Elbette sorununuzu çözmek için yapıyorum , ancak size satır numaralı çıktı vermemek için.)

Bu, girişin satırlarını numaralandırır, böylece bölüm işaretleyicilerinden ayırabilir ve daha sonra tekrar nasıl bir araya getireceğimizi biliriz. Döngünün ana gövdesi, bölüm işaretleyicilerinin üzerinde satır numaraları olduğu göz önünde bulundurularak ilk denemenize dayanmaktadır. İki dosyalarına ayrı girişi keser: file0(inaktif; değil bir bölümünde) ve file1(aktif; in bir bölümü). Yukarıdaki girdi için böyle görünüyorlar:

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

file1:
     4  line X
     5  line Y
     6  line Z

Sonra çalıştırmak file1oluşan birleşik olan ( bütün büyük harf filtreleme yoluyla bölüm içi satırların ; filtrelenmemiş kesit dışı çizgilerle birleştirin; onları orijinal sıralarına geri koymak için; ve ardından satır numaralarını çıkarın. Bu, cevabımın en üstünde gösterilen çıktıyı üretir.

Bu, filtrenizin satır numaralarını tek başına bıraktığını varsayar. Eğer yapmazsa (örneğin, satırın başına karakter ekler veya siler), o zaman, bu genel yaklaşımın hala kullanılabileceğine inanıyorum, ancak biraz daha karmaşık kodlama gerektirecektir.


nlzaten işin çoğunu orada yapıyor - -delimiter seçeneği bunun için.
mikeserv

0

Sınırlandırılmamış satır yığınlarını çıktılamak ve sınırlanmış satır yığınlarını bir filtre programına beslemek için sed kullanan bir kabuk komut dosyası:

#!/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"

Bir dosya adında detagger.sh içine bu senaryoyu yazdım ve böylece kullandı: ./detagger.sh infile.txt. Sorudaki filtreleme işlevini taklit etmek için ayrı bir filter.sh dosyası oluşturdum:

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

Ancak filtreleme işlemi kodda değiştirilebilir.

Numaralandırma hatları gibi işlemlerin ek / dahili sayım gerektirmemesi için genel bir çözüm fikrini takip etmeye çalıştım . Komut dosyası, demarcator etiketlerinin çiftler halinde olduğunu ve iç içe geçmiş etiketleri incelikle işlemediğini görmek için bazı temel denetimler yapar.


-1

Tüm güzel fikirler için teşekkür ederim. Alt bölümü geçici bir dosyada takip ederek ve hepsini bir kerede harici komutuma bağlayarak kendi çözümümü buldum. Bu, Supr'in önerdiğine çok benzer (ancak geçici dosya yerine kabuk değişkeniyle). Ayrıca, sed kullanma fikrini gerçekten seviyorum, ancak bu durumun sözdizimi benim için biraz üstte görünüyor.

Çözümüm:

(Ben nlsadece örnek filtre olarak kullanıyorum)

#!/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

Temp dosyalarını yönetmekle uğraşmak istemem, ancak kabuk değişkenlerinin oldukça düşük boyut sınırlarına sahip olabileceğini anlıyorum ve geçici bir dosya gibi çalışacak herhangi bir bash yapısını bilmiyorum, ancak süreç sona erer.


Ben hatları, mike test verilerini kullanarak, örneğin, öyleyse, “hatları üzerinden birikimine devlet” muktedir istediğini sanıyordum M, Nve Osayılı olacaktır 4, 5ve 6. Bunu yapmaz. Cevabım (mevcut enkarnasyonunda nlbir filtre olarak çalışmadığı gerçeğinin yanı sıra). Eğer bu cevap size istediğiniz çıktıyı veriyor, o zaman “hatları üzerinden biriktiği devlet” ne demek istedin? Eğer sadece durumu korumak istediğini kastettiniz yoluyla değil, her bölüm arasında bölümlere (karşısında)? (Sorunuza neden çok bölümlü bir örnek eklemediniz?)
Scott

@Scott - nl -palmak için kullanın M,N,O==4,5,6.
mikeserv

Diğer yorumlamanın eşit derecede ilginç olduğunu düşünmeme rağmen, sadece alt bölümdeki durumu korumakla ilgilendiğimi açıklığa kavuşturmak için soruyu güncelledim.
James Scriven
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.