bash trova le righe che iniziano con la stringa


10

Ho un sacco di file e voglio trovare quale contiene linee sequenziali che iniziano con una determinata stringa.

Ad esempio per il seguente file:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Cyyyyyyyyy
Czzzzzzzzz
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd
Ceeeeee

C'è più di una riga che inizia con 'C', quindi voglio che questo file sia trovato per comando.
Ad esempio per il seguente file:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd

C'è sempre una riga che inizia con 'C', non voglio questo file. Ho pensato di usare a grepo a sedma non so esattamente come farlo. Forse usando una regexp ^C.*$^Co qualcosa del genere. Qualche idea ?


Ci sono due righe che iniziano con Cnel tuo secondo esempio.
cuonglm

5
Questa domanda non è chiara. Stai cercando file con più di una riga consecutiva che inizia con C?
Graeme,

Sì, questo è quello che voglio. Scusa per il fraintendimento.
Jérémie,

2
@terdon, sembra che le ricerche su più righe con -P abbiano funzionato fino alla 2.5.4 e non più dopo, anche se non riesco a trovare nulla nel log delle modifiche che spiegherebbe il perché.
Stéphane Chazelas,

1
@Graeme potresti voler annullare l'eliminazione della tua risposta, vedi il commento di Stephane, a quanto pare funziona per alcune grepversioni precedenti .
terdon

Risposte:


5

Con pcregrep:

pcregrep -rMl '^C.*\nC' .

POSIXly:

find . -type f -exec awk '
  FNR==1 {last=0; printed=0; next}
  printed {next}
  /^C/ {if (last) {print FILENAME; printed=1; nextfile} else last=1; next}
  {last=0}' {} +

(sebbene ciò significhi leggere completamente tutti i file con quelle awkimplementazioni che non supportano nextfile).


Con versioni di GNU grepfino alla 2.5.4:

grep -rlP '^C.*\nC' .

sembra funzionare, ma è per caso e non è garantito che funzioni.

Prima che fosse risolto in 2.6 (con questo commit ), GNU grepaveva trascurato che la funzione di ricerca pcre che stava usando corrispondeva all'intero buffer attualmente elaborato grep, causando ogni tipo di comportamento sorprendente. Per esempio:

grep -P 'a\s*b'

corrisponderebbe a un file contenente:

bla
bla

Questo corrisponderebbe:

printf '1\n2\n' | grep -P '1\n2'

Ma questo:

(printf '1\n'; sleep 1; printf '2\n') | grep -P '1\n2'

O:

(yes | head -c 32766; printf '1\n2\n') > file; grep -P '1\n2' file

no (dato che 1\n2\nè attraverso due buffer elaborati da grep).

Quel comportamento finì per essere documentato però:

15- Come posso abbinare tra le linee?

Il grep standard non può farlo, poiché è fondamentalmente basato sulla linea. Pertanto, il semplice utilizzo della classe di caratteri "[: space:]" non corrisponde alle nuove righe nel modo previsto. Tuttavia, se il tuo grep è compilato con gli schemi Perl abilitati, il modificatore del 's' (che rende '.' Corrispondente alle nuove righe) può essere usato:

     printf 'foo\nbar\n' | grep -P '(?s)foo.*?bar'

Dopo che è stato corretto in 2.6, la documentazione non è stata modificata (una volta l'ho riportata ).


C'è qualche motivo per non usare exite -exec \;invece di nextfile?
terdon

@terdon, ciò significherebbe eseguirne uno awkper file. Vorresti farlo solo se il tuo awknon supporta nextfilee hai una grande proporzione di file di grandi dimensioni e con linee corrispondenti all'inizio del file.
Stéphane Chazelas,

Che ne dici di questa tecnica grep (immagino con versioni più recenti di GNU grep) che facilita le corrispondenze multilinea facendo apparire l'intero file come una singola stringa impostando il terminatore di riga su NUL - sapresti se ci sono delle limitazioni?
Iruvar,

1
@ 1_CR, carica l'intero file in memoria se non ci sono caratteri NUL e presuppone che le righe non contengano caratteri NUL. Si noti inoltre che le vecchie versioni di GNU grep (che il PO ha) non possono utilizzare -zcon -P. Non c'è \Nsenza -P, dovresti scriverlo $'[\01-\011\013-\0377]'che funzionerebbe solo in C locali (vedi thread.gmane.org/gmane.comp.gnu.grep.bugs/5187 )
Stéphane Chazelas

@StephaneChazelas, dettaglio molto utile, grazie
iruvar

2

Con awk:

awk '{if (p ~ /^C/ && $1 ~ /^C/) print; p=$1}' afile.txt

Questo stamperà il contenuto del file se ci sono righe consecutive che iniziano con a C. L'espressione (p ~ /^C/ && $1 ~ /^C/)esaminerà le righe successive nel file e valuterà vero se il primo carattere in entrambe corrisponde C. In tal caso, la riga verrà stampata.

Per trovare tutti i file che hanno un tale schema, puoi eseguire il awk sopra tramite un findcomando:

find /your/path -type f -exec awk '{if (p ~ /^C/ && $1 ~ /^C/) {print FILENAME; exit;} p=$1}' {} \;

In questo comando, il find+ execpasserà attraverso ciascuno dei file ed eseguirà un awkfiltro simile su ciascun file e stamperà il suo nome FILENAMEse l'espressione awk viene valutata come vera. Per evitare di stampare FILENAMEpiù volte per un singolo file con più corrispondenze exitviene utilizzata la dichiarazione (grazie @terdon).


La mia domanda non era abbastanza chiara, voglio sapere il nome dei file con più di una riga consecutiva a partire daC
Jérémie

@ Jérémie ho aggiornato la mia risposta.
MK

Potresti per favore aggiungere una spiegazione di come funziona? Inoltre, non è necessario flag, solo exitinvece. In questo modo, non è necessario continuare a elaborare i file dopo che è stata trovata una corrispondenza.
terdon

2

Ancora un'altra opzione con GNU sed:

Per un singolo file:

sed -n -- '/^C/{n;/^C/q 1}' "$file" || printf '%s\n' "$file"

(anche se riporterà anche i file che non può leggere).

Per find:

find . -type f ! -exec sed -n '/^C/{n;/^C/q 1}' {} \; -print

Il problema con i file illeggibili in fase di stampa può essere evitato scrivendolo:

find . -type f -size +2c -exec sed -n '$q1;/^C/{n;/^C/q}' {} \; -print

Potete per favore dettagliare il sed -n '$q1;/^C/{n;/^C/q}'?
Jérémie,

Qualcuno a spiegarmi?
Jérémie,

@ Jérémie $q1- forza sed ad uscire con un errore se non viene trovato il pattern. Inoltre finirà con un errore se qualcosa non va nel file (è illeggibile o rotto). Quindi si chiuderà con 0 stato di uscita solo nel caso in cui venga trovato il motivo e verrà passato alla stampa. Parte con /^C/{n;/^C/qè piuttosto semplice. Se trova una stringa che inizia con C leggerà la riga successiva e se inizia anche con C si chiuderà con zero stato di uscita.
corsa il

1

Supponendo che i tuoi file siano abbastanza piccoli da poter essere letti in memoria:

perl -000ne 'print "$ARGV\n" if /^C[^\n]*\nC/sm' *

Spiegazione:

  • - 000: impostato \n\ncome separatore record, attiva la modalità paragrafo che tratta i paragrafi (separati da nuove righe consecutive) come singole righe.
  • -ne: applica lo script fornito come argomento -ea ciascuna riga dei file di input.
  • $ARGV : è il file attualmente in elaborazione
  • /^C[^\n]*\nC/: corrisponde Call'inizio di una riga (vedere la descrizione dei smmodificatori di seguito per il motivo per cui funziona qui) seguita da 0 o più caratteri non di nuova riga, una nuova riga e quindi un'altra C. In altre parole, trova le righe consecutive che iniziano con C. * //sm: questi modificatori di corrispondenza sono (come documentato [qui]):

    • m : considera la stringa come più righe. Cioè, cambia "^" e "$" dalla corrispondenza dell'inizio o della fine della riga solo alle estremità sinistra e destra della stringa per farli corrispondere ovunque all'interno della stringa.

    • s : considera la stringa come una riga singola. Cioè, cambia "." per abbinare qualsiasi personaggio, anche una nuova riga, che normalmente non corrisponderebbe.

Potresti anche fare qualcosa di brutto come:

for f in *; do perl -pe 's/\n/%%/' "$f" | grep -q 'C[^%]*%%C' && echo "$f"; done

Qui, il perlcodice sostituisce le nuove righe con %%così, supponendo che tu non abbia %%nel tuo file di input (grande se ovviamente), grepcorrisponderà a righe consecutive a partire da C.


1

SOLUZIONE:

( set -- *files ; for f ; do (
set -- $(printf %c\  `cat <$f`)
while [ $# -ge 1 ] ;do [ -z "${1#"$2"}" ] && {
    echo "$f"; break ; } || shift
done ) ; done )

DEMO:

Innanzitutto, creeremo una base di test:

abc="a b c d e f g h i j k l m n o p q r s t u v w x y z" 
for l in $abc ; do { i=$((i+1)) h= c= ;
    [ $((i%3)) -eq 0 ] && c="$l" h="${abc%"$l"*}"
    line="$(printf '%s ' $h $c ${abc#"$h"})"
    printf "%s$(printf %s $line)\n" $line >|/tmp/file${i}
} ; done

Quanto sopra crea 26 file nel /tmpnome file1-26. In ogni file ci sono 27 o 28 righe che iniziano con le lettere a-ze seguite dal resto dell'alfabeto. Ogni terzo file contiene due righe consecutive in cui viene duplicato il primo carattere.

CAMPIONE:

cat /tmp/file12
...
aabcdefghijkllmnopqrstuvwxyz
babcdefghijkllmnopqrstuvwxyz
cabcdefghijkllmnopqrstuvwxyz
...
kabcdefghijkllmnopqrstuvwxyz
labcdefghijkllmnopqrstuvwxyz
labcdefghijkllmnopqrstuvwxyz
mabcdefghijkllmnopqrstuvwxyz
...

E quando cambio:

set -- *files

per:

set -- /tmp/file[0-9]*

Ottengo...

PRODUZIONE:

/tmp/file12
/tmp/file15
/tmp/file18
/tmp/file21
/tmp/file24
/tmp/file3
/tmp/file6
/tmp/file9

Quindi, in breve, la soluzione funziona così:

sets subshell posizionali su tutti i tuoi file, e per ciascuno

sets i posizionali di una subshell nidificata rispetto alla prima lettera di ogni riga in ciascun file mentre si muove.

[ tests ]se $1nega $2indicando una corrispondenza, e in tal caso

echoesil nome del file quindi breakè l'iterazione del ciclo corrente

altro shifts al prossimo singolo carattere posizionale per riprovare


0

Questo script utilizza grepe cutper ottenere i numeri di riga delle righe corrispondenti e verifica la presenza di due numeri consecutivi. Si presume che il file abbia un nome file valido passato come primo argomento allo script:

#!/bin/bash

checkfile () {
 echo checking $1
 grep -n -E "^C.*$" $1 | cut -d: -f1 | while read linenum
     do
        : $[ ++PRV ] 
        if [ $linenum == $PRV ]; then return 1; fi
        PRV=$linenum
     done
     return 0
}

PRV="-1"
checkfile $1
if [ $? == 0 ]; then
   echo Consecutive matching lines found in file $1
fi
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.