2010-04-12 19 views
10

Chcę wyodrębnić podciąg pasujący do wzorca i zapisać go w pliku. Przykładem ciąg:Zapisz część pasującego wzorca do zmiennej

Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk 

chcę wyodrębnić część w nawiasach, w tym przypadku [sdf].

Próbowałem zrobić coś takiego, jak grep -e '[$subtext]', aby zapisać tekst w nawiasach do zmiennej. Oczywiście to nie działa, ale szukam sposobu podobnego do tego. Byłoby bardzo elegancko zawierać zmienną w takim wyrażeniu. Co mogę zrobić najlepiej?

Dzięki!

+0

Zakładam, że przechwytywanie powinno wynosić ** między ** nawiasami, tj. niezwiązane z prawem? –

Odpowiedz

7

Nie ma chyba lepszego sposobu korzystania tylko bash, ale:

echo 'Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk' \ 
| sed -s 's/.*\[\(.*\)\].*/\1/' 

Jurgen Jak zaznacza, to pasuje niedopasowanych linie. Jeśli nie chcesz wyprowadzać niepasujących linii, użyj "-n", aby nie wyprowadzać wzoru, a "/ p", aby wyprowadzić wzorzec po dopasowaniu.

| sed -n 's/.*\[\(.*\)\].*/\1/p' 
+1

Powoduje to również wypisywanie niepasujących linii. –

+0

@Jurgen Hotzel: Dzięki, poprawiono poprawkę. – Stephen

+0

Użytkownicy systemu OS X muszą usunąć opcję -s, ponieważ nie jest ona obsługiwana. –

4

Mecz przeciwko regex zastąpić stosując grupowanie i drukować tylko jeśli regex dopasowane:

sed -n "s/.*\[\(.*\)\].*/\1/p" 
0

sed jest zachłanny, więc SED odpowiedzi będą przegapić niektóre dane, jeśli istnieje więcej [] par w Twoje dane. Za pomocą grep + tr roztworu można też stosować awk

$ cat file 
[sss]Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk [tag] blah blah 

$ awk -F"[" '{for(i=2;i<=NF;i++){if($i~/\]/){sub("].*","",$i)};print $i}}' file 
sss 
sdf 
tag 
10

BASH_REMATCH to tablica zawierająca grupy dopasowane przez powłokę.

$ line='Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk' 
$ [[ $line =~ \[([^]]+)\] ]]; echo "${BASH_REMATCH[1]}" 
sdf 

Jeśli chcesz umieścić to w pętli, możesz to zrobić; Oto przykład:

while read -r line; do 
    if [[ $line =~ \[([^]]+)\] ]] ; then 
    drive="${BASH_REMATCH[1]}" 
    do_something_with "$drive" 
    fi 
done < <(dmesg | egrep '\[([hsv]d[^]]+)\]') 

Takie podejście stawia żadnych połączeń zewnętrznych do pętli - tak powłoka nie musi fork i exec do uruchamiania programów zewnętrznych, takich jak sed lub grep. Jako taki jest prawdopodobnie znacznie czystszy niż inne proponowane tu rozwiązania.

Przy okazji, twoje początkowe podejście (używając grep) nie było tak dalekie; stosując grep -o wyjście będzie tylko podciąg pasujący:

$ subtext=$(egrep -o "\[[^]]*\]" <<<"$line") 

... chociaż ten obejmuje uchwyty wewnątrz wychwytywania, a zatem nie jest w 100% poprawne.

+0

, ale następnie bash podczas gdy pętla odczytu jest znacznie wolniejsza do iterowania dużego pliku w porównaniu do awk (etc). Nawiasem mówiąc, nie otrzymuję danych wyjściowych dla pierwszej wersji bez pętli while. ']' W twoich zakresach znaków nie powinno być zmienione. – ghostdog74

+0

@ghostdog - zaktualizowano, dzięki. I * do * otrzymuję dane wyjściowe tak jak jest, ale to bash 4. Zgadzam się, że pętla odczytu jest wolna - filtrowanie raz na stronie wejściowej jest znacznie lepsze niż filtrowanie w pętli, a ty * masz * mieć pętla, jeśli chcesz dopasować więcej niż jedną linię. –

+0

@CharlesDuffy Myślę, że możesz odpowiedzieć na https://unix.stackexchange.com/a/413680/176227 potrzebne podobne rozwiązanie. Wystarczy poprawić wyrażenie regularne. – alhelal

Powiązane problemy