bash dizeyle başlayan satırları bul


10

Bir sürü dosya var ve belirli bir dize ile başlayan sıralı satırlar içeren bulmak istiyorum.

Örneğin, aşağıdaki dosya için:

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

'C' ile başlayan birden fazla satır var, bu yüzden bu dosyanın komutla bulunmasını istiyorum.
Örneğin, aşağıdaki dosya için:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd

Her zaman 'C' ile başlayan bir satır vardır, bu dosyayı istemiyorum. Bir grepveya bir kullanmayı düşündüm sedama nasıl yapılacağını tam olarak bilmiyorum. Belki bir normal ifade ^C.*$^Cya da bunun gibi bir şey kullanıyor olabilirsiniz. Herhangi bir fikir ?


Cİkinci örneğinizle başlayan iki satır var .
cuonglm

5
Bu soru belirsiz. Birden fazla ardışık satırı olan dosyaları mı arıyorsunuz C?
Graeme

Evet, istediğim bu. Yanlış anlaşılma için üzgünüm.
Jérémie

2
@terdon, -P ile çok satırlı aramalar 2.5.4'e kadar çalıştı ve bundan sonra değil, ancak değişiklik günlüğünde nedenini açıklayacak bir şey bulamıyorum.
Stéphane Chazelas

1
@ Cevapınızı geri almak isteyebilirsiniz, Stephane'nin yorumuna bakın, görünüşe göre bazı eski grepsürümlerde işe yarıyor .
terdon

Yanıtlar:


5

İle 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}' {} +

(bu awk, desteklemeyen uygulamalarla tüm dosyaları tam olarak okumak anlamına gelir nextfile).


2.5.4'e grepkadar GNU sürümleriyle :

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

işe yarıyor gibi görünüyor , ancak kazara olduğu ve çalışacağı garanti edilmez.

2.6'da düzeltilmeden önce ( bu taahhütle ) GNU grep, kullandığı pcre arama fonksiyonunun şu anda işlenen tüm tamponla eşleşeceğini ve grepher türlü şaşırtıcı davranışa neden olacağını göz ardı etmişti . Örneğin:

grep -P 'a\s*b'

aşağıdakileri içeren bir dosyayla eşleşir:

bla
bla

Bu eşleşir:

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

Ama bu:

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

Veya:

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

olmaz ( 1\n2\ntarafından işlenen iki tampon arasında olduğu gibi grep).

Bu davranış yine de belgelenmiştir:

15- Çizgiler arasında nasıl eşleşebilirim?

Temelde çizgi tabanlı olduğu için standart grep bunu yapamaz. Bu nedenle, yalnızca '[: space:]' karakter sınıfını kullanmak, yeni satırları beklediğiniz şekilde eşleştirmez. Ancak, grep'iniz Perl desenleri etkin olarak derlenmişse, Perl 's' değiştiricisi ('.' Eşleme satırlarını eşleştirir) kullanılabilir:

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

2.6'da düzeltildikten sonra, belgeler değiştirilmedi (bir kez orada rapor ettim ).


Kullanmamak için bir sebep var mı exitve -exec \;yerine nextfile ait?
terdon

@terdon, bu awkdosya başına bir tane çalıştırmak anlamına gelir . Bunu yalnızca sizin awkdesteklemediğiniz nextfileve büyük ve dosyanın başlangıcına doğru eşleşen satırlara sahip büyük bir oranda dosyanız varsa yapmak istersiniz .
Stéphane Chazelas

Hat sonlandırıcıyı NUL olarak ayarlayarak tüm dosyanın tek bir dize gibi görünmesini sağlayarak çok satırlı eşleşmeleri kolaylaştıran bu grep tekniğine (sanırım GNU grep'in daha yeni sürümleriyle) - herhangi bir sınırlama olup olmadığını biliyor musunuz?
iruvar

1
@ 1_CR, İçinde NUL karakteri yoksa ve satırların NUL karakteri içermediğini varsayarsa, tüm dosya belleğe yüklenir. Ayrıca GNU grep'in (OP'nin sahip olduğu) eski sürümlerinin -zbirlikte kullanılamayacağını da unutmayın -P. \NOlmadan hayır -P, $'[\01-\011\013-\0377]'sadece C yerel ayarlarında işe yarayacak bir yazı yazmanız gerekir (bkz. Thread.gmane.org/gmane.comp.gnu.grep.bugs/5187 )
Stéphane Chazelas

@StephaneChazelas, çok faydalı detay, teşekkürler
iruvar

2

İle awk:

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

Bu, a ile başlayan ardışık satırlar varsa dosyanın içeriğini yazdırır C. İfade (p ~ /^C/ && $1 ~ /^C/), dosyadaki birbirini izleyen satırlara bakar ve her iki karakterdeki ilk karakter eşleşirse true olarak değerlendirilir C. Bu durumda, çizgi yazdırılır.

Böyle bir desene sahip tüm dosyaları bulmak için yukarıdaki awk'ı bir findkomutla çalıştırabilirsiniz :

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

Bu komutta, find+ execdosyaların her birini gözden geçirecek ve awkher dosyada benzer filtreleme gerçekleştirecek FILENAMEve awk ifadesi true olarak değerlendirilmişse adını yazdıracaktır . FILENAMEBirden fazla eşleşmeye sahip tek bir dosya için birden çok kez yazdırmayı önlemek için exitifade kullanılır (teşekkürler @terdon).


Sorum yeterince açık değildi, birden fazla ardışık satırı olan dosyaların ismini bilmek istiyorumC
Jérémie

@ Jérémie Cevabımı güncelledim.
mkc

Bunun nasıl çalıştığına dair bir açıklama ekleyebilir misiniz? Ayrıca, orada gerek var flag, sadece exityerine. Bu şekilde, bir eşleşme bulunduktan sonra dosyaları işlemeye devam etmenize gerek yoktur.
terdon

2

GNU ile başka bir seçenek sed:

Tek bir dosya için:

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

(yine de okuyamadığı dosyaları rapor edecektir).

Şunun için find:

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

Yazdırılamayan dosyaların yazdırılmasındaki sorun, yazılarak önlenebilir:

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

Lütfen detay verebilir misiniz sed -n '$q1;/^C/{n;/^C/q}'?
Jérémie

Beni açıklayacak kimse var mı?
Jérémie

@ Jérémie $q1- desen bulunmazsa sed'i bir hata ile bırakmaya zorlar. Dosyada bir sorun varsa (okunamaz veya bozuk) hata ile de bitirilir. Bu nedenle, yalnızca desen bulunması durumunda 0 çıkış durumundan çıkacak ve yazdırmaya geçirilecektir. Parçası ile /^C/{n;/^C/qoldukça basit. C ile başlayan bir dize bulursa bir sonraki satırı okuyacak ve C ile başlıyorsa sıfır çıkış durumuyla çıkacaktır.
acele

1

Dosyalarınızın belleğe okunacak kadar küçük olduğunu varsayarsak:

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

Açıklama:

  • - 000: \n\nkayıt ayırıcı olarak ayarlandığında , paragrafları (ardışık satırlarla ayrılmış) tek satır olarak ele alacak paragraf modunu açar.
  • -ne: bağımsız değişken olarak verilen komut -edosyasını giriş dosyalarının her satırına uygular .
  • $ARGV : şu anda işlenmekte olan dosya
  • /^C[^\n]*\nC/: Cbir satırın başlangıcında eşleşir (bunun smneden işe yaradığını görmek için aşağıdaki değiştiricilerin açıklamasına bakın ) ve ardından 0 veya daha fazla yeni satır olmayan karakter, yeni satır ve sonra başka bir C yazın. Başka bir deyişle, ile başlayan ardışık satırları bulun C. * //sm: bu eşleme düzenleyiciler ([burada] olarak belgelendiği gibi):

    • m : Dizeyi birden çok satır olarak ele al. Diğer bir deyişle, "^" ve "$" ifadelerini, dizenin yalnızca sol ve sağ uçlarında, dizenin herhangi bir yerinde eşleşecek şekilde değiştirin.

    • s : Dizeyi tek satır olarak ele al. Yani, değiştirmek "." normalde eşleşmeyeceği herhangi bir karakteri, hatta bir yeni satırı eşleştirmek için.

Çirkin bir şey de yapabilirsiniz:

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

Burada perlkod ile yeni satırı %%hiçbir var varsayarak, bu yüzden %%(büyük girişinizi dosyasında ise tabii ki), grepile başlayan ardışık satırları maç olacak C.


1

ÇÖZÜM:

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

DEMO:

İlk olarak, bir test tabanı oluşturacağız:

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

Yukarıda /tmpadlandırılmış 26 dosya oluşturur file1-26. Her dosyada, harflerle başlayan a-zve ardından alfabenin geri kalanıyla başlayan 27 veya 28 satır vardır . Her 3. dosyada, ilk karakterin çoğaltıldığı iki ardışık satır bulunur.

ÖRNEKLEM:

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

Ve değiştiğimde:

set -- *files

için:

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

Alırım...

ÇIKTI:

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

Kısacası, çözüm şöyle çalışır:

sets tüm dosyalarınıza ve her biri için alt kabuk konumlandırmaları

sets her döngüdeki her dosyadaki her bir satırın ilk harfine yuvalanmış bir alt kabuk konumlandırır.

[ tests ]eşleşmeyi gösteren $1olumsuzluklar varsa $2ve eğer öyleyse

echoesDosya adı daha sonra breaks döngünün

Başka shiftler sonraki tek karakter pozisyonel için yeniden denemek için


0

Bu komut dosyası , eşleşen satırların satır numaralarını almak için grepve öğelerini kullanır ve cutart arda iki sayıyı denetler. Dosyanın, komut dosyasına ilk argüman olarak geçirildiği geçerli bir dosya adı olduğu varsayılır:

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