Filtra o reindirizza determinate sezioni di un file


14

Ho un file di input con alcune sezioni che sono delimitate con tag di inizio e fine, ad esempio:

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

Voglio applicare una trasformazione a questo file in modo tale che le righe X, Y, Z vengano filtrate tramite alcuni comandi ( nlad esempio), ma il resto delle righe passa invariato. Si noti che nl(linee numeriche) accumula stato attraverso le linee, quindi non è una trasformazione statica che viene applicata a ciascuna delle linee X, Y, Z. ( Modifica : è stato sottolineato che nlpuò funzionare in una modalità che non richiede lo stato accumulato, ma sto solo usando nlcome esempio per semplificare la domanda. In realtà il comando è uno script personalizzato più complesso. Quello che sto davvero cercando for è una soluzione generica al problema dell'applicazione di un filtro standard a una sottosezione di un file di input )

L'output dovrebbe apparire come:

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

Nel file possono essere presenti diverse sezioni che richiedono la trasformazione.

Aggiornamento 2 In origine non avevo specificato cosa dovrebbe accadere se esiste più di una sezione, ad esempio:

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

La mia aspettativa sarebbe che lo stato debba essere mantenuto solo all'interno di una data sezione, dando:

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

ma penso che interpretare il problema come se fosse necessario mantenere lo stato tra le sezioni sia valido e utile in molti contesti.

Fine aggiornamento 2

Il mio primo pensiero è quello di costruire una semplice macchina a stati che tenga traccia della sezione in cui ci troviamo:

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

Con cui corro:

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

Questo non funziona poiché ogni chiamata a nlè indipendente, quindi i numeri di linea non aumentano:

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

Il mio prossimo tentativo fu di usare un 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

Questo dà l'output corretto, ma nell'ordine sbagliato:

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

Probabilmente c'è un po 'di cache in corso.

Sto sbagliando tutto? Sembra un problema piuttosto generico. Sento che dovrebbe esserci una semplice pipeline che risolva questo problema.


nlnon deve accumulare stato . Guardate nl -de controllare le man/ infopagine per le informazioni sul nl's delimitatore sezione .
Mikeserv,

nl è solo un esempio. Nel mio caso sto eseguendo uno script personalizzato anziché nl.
James Scriven,

In tal caso, chiarisci cosa sta facendo il tuo script.
terdon

Ho chiarito nella domanda che sto usando solo nlcome filtro di esempio. Ho pensato che avrebbe semplificato la domanda rendendo più chiari i dettagli di cosa stesse facendo esattamente il filtro, ma probabilmente ho solo causato più confusione. In effetti, sto filtrando la sottosezione attraverso un evidenziatore di codice, per un generatore di blog statico cresciuto in casa. In questo momento sto usando gnu source-highlight, ma ciò potrebbe cambiare e potrei aggiungere più filtri, come anche un formattatore.
James Scriven,

Risposte:


7

Sono d'accordo con te - probabilmente è un problema generico. Tuttavia, alcune utility comuni hanno alcune funzionalità per gestirlo.


nl

nl, ad esempio, separa l'input in pagine logiche come -deliminato da un delimitatore di sezione a due caratteri . Tre occorrenze su una riga indicano da sole l'inizio di un'intestazione , due il corpo e uno il piè di pagina . Sostituisce uno qualsiasi di questi trovati nell'input con una riga vuota nell'output - che sono le uniche righe vuote che abbia mai stampato

Ho modificato il tuo esempio per includere un'altra sezione e inserirlo ./infile. Quindi sembra così:

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

Quindi ho eseguito il seguente:

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

nlsi può dire di accumulare stato attraverso pagine logiche, ma non per impostazione predefinita. Invece numererà le linee del suo input secondo gli stili e per sezione . Quindi -hasignifica numerare tutte le linee di intestazione e -bnsignifica nessuna linea del corpo - come inizia in un corpo dello Stato.

Fino a quando non l'ho imparato, lo usavo nl per qualsiasi input, ma dopo aver realizzato che ciò nlpoteva distorcere l'output in base al suo -delimitatore predefinito, \:ho imparato a stare più attento e ho iniziato a utilizzare l' grep -nF ''input non testato. Ma un'altra lezione appresa quel giorno è stata che nlpuò essere utilmente applicato sotto altri aspetti - come questo - se modifichi solo un po 'il suo input - come faccio sedsopra.

PRODUZIONE

  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

Ecco qualcosa in più nl: noti sopra come tutte le linee tranne quelle numerate iniziano con gli spazi? Quando le nllinee dei numeri inserisce un certo numero di caratteri nella testa di ciascuno. Per quelle righe non numera - nemmeno gli spazi vuoti - corrisponde sempre al rientro inserendo (-w numera idth count + eparator -slen) * spazi all'inizio di linee non numerate. Ciò consente di riprodurre esattamente il contenuto non numerato confrontandolo con il contenuto numerato e con poco sforzo. Se consideri che nldividerà il suo input in sezioni logiche per te e che puoi inserire -sstringhe arbitrarie all'inizio di ogni riga che numera, allora diventa abbastanza facile gestire il suo output:

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

Le stampe sopra ...

                                        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

Se nlnon è l'applicazione di destinazione, una GNU sedpuò eeseguire un comando di shell arbitrario per te a seconda di una corrispondenza.

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

Sopra sedraccoglie input nello spazio modello fino a quando non ha abbastanza per passare con successo l' Test di sostituzione e interrompere bil :lranching all'abel. Quando lo fa, viene eelaborato nlcon input rappresentato come un <<documento qui per tutto il resto del suo spazio-modello.

Il flusso di lavoro è così:

  1. /^@@.*start$/!b
    • se un ^intera linea $non !non /corrisponde /al modello di cui sopra, allora è bun ranch fuori dalla sceneggiatura e autoprinted - quindi da questo punto in poi stiamo lavorando solo con una serie di linee che ha avuto inizio con il modello.
  2. s//nl <<\\@@/
    • il s//campo vuoto sostituisce l' /ultimo indirizzo sedtentato di corrispondere, quindi questo comando sostituisce invece l'intera @@.*startriga nl <<\\@@.
  3. :l;N
    • Il :comando definisce un'etichetta di ramo - qui ho impostato uno chiamato :label. Il Ncomando ext aggiunge la riga successiva di input allo spazio pattern seguito da un \ncarattere ewline. Questo è uno dei pochi modi per ottenere una \newline in uno sedspazio modello: il \npersonaggio ewline è un delimitatore sicuro per un sedder che lo ha fatto per un po '.
  4. s/\(\n@@\)[^\n]*end$/\1/
    • questo s///ubstitution può avere successo soltanto dopo un avvio viene rilevato e solo sulla prima a seguito del verificarsi di un fine linea. \nAgirà solo su uno spazio modello in cui la linea finale finale è immediatamente seguita @@.*endcontrassegnando la fine $dello spazio modello. Quando agisce, sostituisce l'intera stringa corrispondente con il \1primo \(gruppo \)o \n@@.
  5. Tl
    • il Tcomando est si ramifica su un'etichetta (se fornita) se non si è verificata una sostituzione riuscita dall'ultima volta in cui una riga di input è stata trascinata nello spazio modello (come faccio io con / N) . Ciò significa che ogni volta che una \newline viene aggiunta allo spazio del pattern che non corrisponde al delimitatore finale, il Tcomando est ha esito negativo e si ramifica di nuovo :lsull'abel, il che si traduce in sedpull in Next line e looping fino al successo.
  6. e

    • Quando la sostituzione per la partita finale è successo e lo script non lo fa di nuovo ramo per un fallito Test, sedsarà execute un comando che looks come questo:

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

Puoi vederlo da solo modificando l'ultima riga lì per apparire Tl;l;e.

Stampa:

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

Un ultimo modo per farlo, e forse il modo più semplice, è usare un while readloop, ma per una buona ragione. La shell - (soprattutto una bashshell) - è in genere piuttosto spaventosa nel gestire input in grandi quantità o in flussi costanti. Anche questo ha senso: il lavoro della shell è gestire l'input carattere per carattere e richiamare altri comandi in grado di gestire le cose più grandi.

Ma soprattutto sul suo ruolo c'è che la shell non deve read sovrastare gran parte dell'input - è specificato per non bufferizzare l'input o l'output al punto che consuma così tanto o non trasmette abbastanza nel tempo che i comandi che chiama rimangono mancanti - al byte. Quindi readrende un test di input eccellente - per returninformazioni sulla presenza di input rimanenti e si dovrebbe richiamare il comando successivo per leggerlo - ma in genere non è il modo migliore di procedere.

Ecco un esempio, tuttavia, di come si potrebbero usare read e altri comandi per elaborare l'input in sincronia:

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

La prima cosa che accade per ogni iterazione è readuna riga. Se ha esito positivo significa che il loop non ha ancora colpito EOF e quindi nel casecorrispondente delimitatore di avvio il doblocco viene immediatamente eseguito. Altrimenti, printfstampa $linel'esso reade sedsi chiama.

sedsi pRint ogni riga finché non incontra l' inizio marcatore - quando qUITS ingresso interamente. Lo -uswitch nbuffered è necessario per GNU sedperché altrimenti può bufferizzare avidamente, ma - secondo le specifiche - altri POSIX seddovrebbero funzionare senza alcuna considerazione speciale - purché <infilesia un file normale.

Quando il primo viene sed qattivato, la shell esegue il doblocco del loop, che chiama un altro sedche stampa ogni riga fino a quando non incontra il marker di fine . Inoltra il suo output a paste, perché stampa i numeri di riga ciascuno sulla propria riga. Come questo:

1
line M
2
line N
3
line O

pastequindi li incolla sui :personaggi e l'intero output appare come:

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

Questi sono solo esempi: qui si può fare qualsiasi cosa nel test o fare blocchi, ma la prima utility non deve consumare troppo input.

Tutte le utility coinvolte leggono lo stesso input - e stampano i loro risultati - ognuna a turno. Questo genere di cose può essere difficile da ottenere il blocco di - perché diversi programmi di utilità saranno tamponare più di altri - ma si possono generalmente contare su dd, heade seddi fare la cosa giusta (anche se, per GNU sed, è necessario il cli-switch) e dovresti sempre essere in grado di fare affidamento read, perché per sua natura è molto lento . Ed è per questo che il loop sopra lo chiama solo una volta per blocco di input.


Ho provato il secondo sedesempio che hai dato, e funziona, ma sto davvero avendo problemi a sintonizzare la sintassi. (la mia sed è piuttosto debole e di solito è limitata a s / findthis / replacethis / g. Dovrò fare uno sforzo per sedermi e capire davvero sed.)
James Scriven

@JamesScriven - Ho appena modificato per spiegarlo meglio. Fammi sapere se non aiuta. Ho anche cambiato molto il comando: ora è in pezzi più piccoli e più sensibili.
Mikeserv,

4

Una possibilità è di farlo con l'editor di testo vim. Può convogliare sezioni arbitrarie tramite comandi shell.

Un modo per farlo è tramite i numeri di riga, usando :4,6!nl . Questo comando ex eseguirà nl sulle righe 4-6 incluse, ottenendo ciò che vuoi sul tuo esempio di input.

Un altro modo più interattivo è selezionare le linee appropriate usando la modalità di selezione della linea (Maiusc-V) e i tasti freccia o la ricerca, e quindi usando :!nl. Una sequenza di comandi completa per l'input di esempio potrebbe essere

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

Questo non è molto adatto all'automazione (le risposte usando ad esempio sed sono migliori per questo), ma per le modifiche una tantum è molto utile non dover ricorrere a shellscript a 20 righe.

Se non hai familiarità con vi (m), dovresti almeno sapere che dopo queste modifiche puoi salvare il file usando :wq.


Sì, vim è fantastico! Ma in questo caso cerco una soluzione gestibile da script.
James Scriven,

@JamesScriven, chiunque dica vim non è programmabile in modo insufficiente. Per prima cosa crea una directory di progetto e in quella directory copia tutti i file di avvio di vim dalla tua home directory (ln -s funziona bene tranne per .vimrc che stiamo per modificare e .viminfo che può essere riempito di rumore). Aggiungi la definizione della funzione che farà il lavoro nel nuovo file .vimrc e poi chiama vim come HOME=$(pwd) vim -c 'call Mf()' f. Se stai usando xargs potresti voler usare gvim su un xserver dedicato per evitare di corrompere il tuo tty (vnc è indipendente dalla scheda video e può essere monitorato).
Hildred

@hildred Hmmm ... Non potrei semplicemente usare [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html ) per simulare i clic del mouse per vim?
James Scriven,

2

La soluzione più semplice a cui riesco a pensare è quella di non usare nlma contare le righe tu stesso:

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

Quindi eseguirlo sul file:

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

Grazie mille. Ho aggiornato la domanda per chiarire che sto cercando una soluzione generica per filtrare una sottosezione di un input, piuttosto l'esempio specifico di righe di numerazione. forse un comando di esempio migliore sarebbe stato "tac" (linee inverse)
James Scriven,

2

Se il tuo obiettivo è inviare l'intero blocco di codice a una singola istanza di processo, potresti accumulare le linee e ritardare il piping fino a raggiungere la fine del blocco di codice:

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

Ciò produce quanto segue per un file di input che ripete il test case tre volte:

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

Per fare qualcosa di diverso con il blocco di codice, ad esempio, invertire e quindi il numero, basta tubo attraverso qualcos'altro: echo -E "${acc:1}" | tac | nl. Risultato:

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

O conteggio parole echo -E "${acc:1}" | wc:

line A
line B
      3       6      21
line C
line D

2

Modifica ha aggiunto un'opzione per definire un filtro fornito dall'utente

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

Per impostazione predefinita, il filtro è "nl". Per modificare il filtro utilizzare l'opzione "-p" con alcuni comandi forniti dall'utente:

codify -p="wc" file

o

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

Quest'ultimo filtro produrrà:

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

Aggiornamento 1 L'uso di IPC :: Open2 ha problemi di ridimensionamento: se si supera la dimensione del buffer, potrebbe bloccarsi. (nella mia macchina il pipe si ingrassa se 64K corrispondono a 10_000 x "linea Y").

Se abbiamo bisogno di cose più grandi (abbiamo bisogno di più i 10000 "linea Y"):

(1) installa e usa use Forks::Super 'open2';

(2) o sostituire la funzione pipeit con:

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

È davvero fantastico. Immagino che i trucchi siano che non stai elaborando riga per riga (mediante la ridefinizione $/e il sflag) e l'uso del eflag per eseguire la chiamata effettiva al comando esterno. Mi piace molto il secondo esempio (ascii art)!
James Scriven,

La cosa che ho notato, però, è che questo non sembra scalare oltre un paio di migliaia di righe nella sottosezione. Sospetto che ciò abbia a che fare con il trattamento della sottosezione come un grande blocco di testo.
James Scriven,

Grazie. Sì: `/ e` = eval; /s= ("." significa (.|\n)); $/ridefinisce il separatore di registri.
JJoao,

@JamesScriven, hai ragione (la pipa sta bloccando). Fammi provare cosa sta succedendo ...
JJoao,

@JamesScriven, per favore, vedi il mio aggiornamento ...
JJoao,

1

Questo è un lavoro per 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}

Quando lo script vede il marcatore di inizio, nota che dovrebbe iniziare il piping nl. Quando la pipevariabile è vera (diversa da zero), l'output viene reindirizzato al nlcomando; quando la variabile è falsa (non impostata o zero), l'output viene stampato direttamente. Il comando piped viene biforcato la prima volta che viene rilevato il costrutto pipe per ogni stringa di comando. Le successive valutazioni dell'operatore del tubo con la stessa stringa riutilizzano il tubo esistente; un valore di stringa diverso creerebbe una pipe diversa. La closefunzione chiude la pipe per la stringa di comando fornita.


Questa è essenzialmente la stessa logica dello script della shell che utilizza una pipe denominata, ma è molto più semplice da precisare e la logica di chiusura è corretta. Devi chiudere la pipa al momento giusto, per far nluscire il comando, svuotando i suoi buffer. Lo script in realtà chiude la pipa troppo presto: la pipa si chiude non appena la prima echo $line >myfifotermina l'esecuzione. Tuttavia, il nlcomando vede la fine del file solo se ottiene un intervallo di tempo prima della successiva esecuzione dello script echo $line >myfifo. Se hai avuto un grande volume di dati o se lo aggiungi sleep 1dopo averlo scritto myfifo, lo vedrainl elabora solo la prima riga o il primo gruppo rapido di righe, quindi esce perché ha visto la fine del suo input.

Usando la tua struttura, dovresti tenere aperto il tubo fino a quando non ti serve più. È necessario disporre di un reindirizzamento di output singolo nel pipe.

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

(Ho anche colto l'occasione per aggiungere una quotazione corretta e simili - vedi Perché il mio script shell si soffoca su spazi bianchi o altri caratteri speciali? )

Se lo stai facendo, potresti anche utilizzare una pipeline anziché una pipa denominata.

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

la tua soluzione awk è davvero bella! Penso che sia di gran lunga la soluzione più concisa (ma molto leggibile). Il comportamento del awk nel riutilizzare la pipe per nl è garantito, oppure potrebbe decidere, "hey, per ora hai convogliato abbastanza ... Ho intenzione di chiudere questa pipe e aprirne una nuova" ?. Anche la tua soluzione "pipeline" è davvero bella. Originariamente ho scontato un approccio con loop while incorporati, poiché pensavo che potesse essere un po 'confuso, ma penso che quello che hai sia fantastico. Manca un punto e virgola prima del do. (Non ho il rappresentante qui per fare una piccola modifica.)
James Scriven,

1
... Non riuscivo a far funzionare la tua soluzione di pipe denominata. Sembra che ci sia una condizione di gara, tale che la sezione convogliata a nl a volte si perde completamente. Inoltre, se c'è una seconda sezione @@ inline-code-start / end, si perde sempre.
James Scriven,

0

OK, prima di tutto; Capisco che non stai cercando un modo per numerare le linee nelle sezioni del tuo file. Dato che non hai fornito un esempio reale di ciò che potrebbe essere il tuo filtro (diverso da nl), supponiamo che lo sia

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

cioè, converti il ​​testo in maiuscolo; quindi, per un input di

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

vuoi un output di

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

Ecco la mia prima approssimazione di una soluzione:

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

dove gli spazi prima delle @@stringhe e vicino alla fine dell'ultima riga sono tabulazioni. Si prega di notare che sto usando nl per i miei scopi . (Ovviamente lo sto facendo per risolvere il tuo problema, ma non per darti un output numerato per riga.)

Questo numera le linee dell'input in modo da poterlo dividere in corrispondenza dei marcatori di sezione e sapere come rimetterlo insieme più tardi. Il corpo principale del loop si basa sul tuo primo tentativo, prendendo in considerazione il fatto che gli indicatori di sezione hanno numeri di riga su di essi. Suddivide l'input in due file: file0(inattivo; non in una sezione) e file1(attivo; in una sezione). Ecco come si presentano per l'input sopra:

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

file1:
     4  line X
     5  line Y
     6  line Z

Quindi corriamo file1(che è la concatenazione di tutti le linee di sezione) attraverso il filtro di maiuscole; combinalo con le linee fuori sezione non filtrate; ordinare, per riportarli nel loro ordine originale; e quindi rimuovere i numeri di riga. Questo produce l'output mostrato nella parte superiore della mia risposta.

Ciò presuppone che il filtro lasci soli i numeri di riga. In caso contrario (ad esempio, se inserisce o elimina i caratteri all'inizio della riga), quindi, credo, questo approccio generale può ancora essere utilizzato, ma richiederà una codifica leggermente più complicata.


nlfa già la maggior parte del lavoro lì - ecco a cosa -dserve la sua opzione di eliminazione.
Mikeserv,

0

Uno script di shell che utilizza sed per generare blocchi di linee non delimitate e inserire blocchi di linee delimitate in un programma di filtro:

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

Ho scritto questo script in un file chiamato detagger.sh e usato come così: ./detagger.sh infile.txt. Ho creato un file filter.sh separato per imitare la funzionalità di filtro nella domanda:

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

Ma l'operazione di filtro può essere modificata nel codice.

Ho provato a seguire l'idea di una soluzione generica in questo modo in modo che operazioni come le linee di numerazione non richiedano un conteggio aggiuntivo / interno. Lo script esegue alcuni controlli rudimentali per verificare che i tag del demarcatore siano in coppia e non gestisce affatto tag nidificati.


-1

Grazie per tutte le grandi idee. Ho trovato la mia soluzione tenendo traccia della sottosezione in un file temporaneo e reindirizzando tutto in una volta al mio comando esterno. Questo è molto simile a quello suggerito da Supr (ma con una variabile shell invece del file temporaneo). Inoltre, mi piace molto l'idea di usare sed, ma la sintassi per questo caso sembra un po 'esagerata per me.

La mia soluzione:

(Uso nlsolo come filtro di esempio)

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

Preferirei non avere a che fare con la gestione dei file temporanei, ma capisco che le variabili della shell possono avere limiti di dimensione piuttosto bassi e non conosco alcun costrutto bash che funzioni come un file temporaneo, ma svanisce automaticamente quando il processo termina.


Pensavo volessi essere in grado di “stato accumulate attraverso le linee”, così, ad esempio, utilizzando i dati di test di Mike, linee M, Ne Osarebbe essere numerati 4, 5e 6. Questo non lo fa. La mia risposta fa (a parte il fatto che, nella sua attuale incarnazione, non funziona nlcome filtro). Se questa risposta ti sta dando l'output desiderato, che cosa intendevi per "accumulare stato attraverso le linee"? Volevi dire che volevi preservare lo stato solo attraverso ogni sezione, ma non tra (attraverso) sezioni? (Perché non hai inserito un esempio in più sezioni nella tua domanda?)
Scott

@Scott - usa nl -pper ottenere M,N,O==4,5,6.
Mikeserv,

Ho aggiornato la domanda per chiarire che sono interessato solo a mantenere lo stato all'interno della sottosezione, anche se penso che l'altra interpretazione sia ugualmente interessante.
James Scriven,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.