Przechwytywanie grup z wyrażenia regularnego Grep

Mam ten mały skrypt w sh (Mac OSX 10.6), aby przejrzeć tablicę plików. Google przestało być pomocne w tym momencie:

files="*.jpg"
for f in $files
    do
        echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
        name=$?
        echo $name
    done

Jak na razie (oczywiście dla was Shell Guru) $name zawiera tylko 0, 1 lub 2, w zależności od tego, czy grep stwierdzi, że nazwa pliku pasuje do podanej sprawy. chciałbym uchwycić to, co jest wewnątrz parens ([a-z]+) i zapisać to do zmiennej .

Chciałbym używać grep tylko, jeśli to możliwe. Jeśli nie, proszę nie Python lub Perl itp. sed albo coś podobnego - jestem nowy w shell i chciałbym zaatakować to z punktu widzenia * Nix purist.

Poza tym, jako super-fajny bonu S, jestem ciekaw, jak Mogę konkatenować string w powłoce? Czy grupa, którą przechwyciłem był łańcuch "somename" przechowywany w $name, i chciałem dodać łańcuch ".jpg " do końca, czy mogę cat $name '.jpg'?

Proszę wyjaśnić, co się dzieje, jeśli masz czas.

Author: royhowie, 2009-12-12

7 answers

Jeśli używasz Basha, nie musisz nawet używać grep:

files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files
do
    if [[ $f =~ $regex ]]
    then
        name="${BASH_REMATCH[1]}"
        echo "${name}.jpg"    # concatenate strings
        name="${name}.jpg"    # same thing stored in a variable
    else
        echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files
    fi
done

Lepiej umieścić regex w zmiennej. Niektóre wzory nie będą działać, jeśli zostaną dołączone dosłownie.

Używa =~, który jest operatorem regex match Basha. Wyniki dopasowania są zapisywane w tablicy o nazwie $BASH_REMATCH. Pierwsza grupa przechwytywania jest przechowywana w indeksie 1, Druga (jeśli istnieje) w indeksie 2, itd. Indeks zero to pełne dopasowanie.

Powinieneś mieć świadomość, że bez anchorów, ten regex (i ten użycie grep) pasuje do dowolnego z poniższych przykładów i więcej, które mogą nie być tym, czego szukasz:

123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz

Aby wyeliminować drugi i czwarty przykład, zrób regex tak:

^[0-9]+_([a-z]+)_[0-9a-z]*

Który mówi, że łańcuch znaków musi zaczynać się z jedną lub więcej cyframi. Karat reprezentuje początek ciągu. Jeśli dodasz znak dolara na końcu wyrażenia regularnego, w ten sposób:

^[0-9]+_([a-z]+)_[0-9a-z]*$

Wtedy trzeci przykład również zostanie wyeliminowany, ponieważ kropka nie jest wśród znaki w regex i znak dolara reprezentują koniec łańcucha. Zauważ, że czwarty przykład również nie pasuje do tego meczu.

Jeśli masz GNU grep (około 2.5 lub później, jak sądzę, kiedy dodano operator \K):

name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg

Operator \K (zmienna długość look-behind) powoduje dopasowanie poprzedniego wzorca, ale nie uwzględnia dopasowania w wyniku. Odpowiednikiem stałej długości jest (?<=) - wzór będzie zawarty przed nawiasem zamykającym. Ty musi używać \K, jeśli kwantyfikatory mogą pasować do ciągów o różnych długościach (np. +, *, {2,4}).

Operator (?=) dopasowuje wzory o stałej lub zmiennej długości i nazywa się "look-ahead". Nie zawiera również dopasowanego ciągu w wyniku.

Aby wielkość dopasowania była niewrażliwa na wielkość liter, używany jest operator (?i). Wpływa na wzorce, które za nim podążają, więc jego pozycja jest znacząca.

Regex może wymagać dostosowania w zależności od tego, czy istnieje są innymi znakami w nazwie pliku. Zauważ, że w tym przypadku, pokazuję przykład łączenia łańcucha w tym samym czasie, że podłańcuch jest przechwytywany.

 382
Author: Dennis Williamson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-07 18:45:21

To nie jest naprawdę możliwe z czystym grep, przynajmniej nie ogólnie.

Ale jeśli wzorzec jest odpowiedni, możesz użyć grep wiele razy w potoku, aby najpierw zredukować linię do znanego formatu, a następnie wyodrębnić tylko ten bit, który chcesz. (Chociaż narzędzia takie jak cut i sed są w tym znacznie lepsze).

Załóżmy, że Twój wzór był nieco prostszy: [0-9]+_([a-z]+)_ możesz wyodrębnić to w następujący sposób:

echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'

Pierwszy grep będzie usuń wszystkie linie, które nie pasują do twojego paternu, drugi grep (który ma --only-matching podany) wyświetli część Alfa nazwy. Działa to tylko dlatego, że wzór jest odpowiedni: "porcja alfa" jest wystarczająco specyficzna, aby wyciągnąć to, co chcesz.

(na bok: osobiście użyłbym grep + cut aby osiągnąć to, czego szukasz: echo $name | grep {pattern} | cut -d _ -f 2. Powoduje to, że cut przetwarza linię na pola przez podział na ogranicznik _ i zwraca tylko pole 2 (numery pól zaczynają się od 1)).

Filozofią Uniksa jest posiadanie narzędzi, które robią jedną rzecz i robią to dobrze, i łączą je w celu osiągnięcia nietrywialnych zadań, więc argumentowałbym, że grep + sed etc jest bardziej Unixy sposób robienia rzeczy: -)

 122
Author: RobM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-12 01:26:04

Zdaję sobie sprawę, że odpowiedź na to pytanie została już zaakceptowana, ale z "ściśle * Nix purystycznego punktu widzenia" wydaje się, że właściwym narzędziem do tego zadania jest pcregrep, o czym jeszcze nie wspomniano. Spróbuj zmienić linie:

    echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
    name=$?

Do:

    name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*')

Aby uzyskać tylko zawartość grupy przechwytywania 1.

The pcregrep narzędzie wykorzystuje tę samą składnię, z którą już korzystałeś grep, ale realizuje funkcjonalność, która potrzebujesz.

Parametr -o działa podobnie jak wersja grep, jeśli jest naga, ale akceptuje również parametr liczbowy w pcregrep, który wskazuje, którą grupę przechwytywania chcesz pokazać.

W tym rozwiązaniu jest wymagane minimum zmian w skrypcie. Wystarczy wymienić jedno modułowe narzędzie na inne i dostosować parametry.

Interesująca Uwaga: możesz użyć argumentów multiple-o, aby zwrócić wiele grup przechwytywania w kolejności w których pojawiają się na linii.

 74
Author: John Sherwood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-03 17:14:08

Nie jest to możliwe tylko w grep I believe

Dla sed:

name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*/\2/'`
[[2]] ja jednak wezmę za premię:
echo "$name.jpg"
 22
Author: cobbal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-12 01:17:33

Jest to rozwiązanie, które wykorzystuje gawk. Jest to coś, co uważam, że muszę często używać, więc stworzyłem dla niego funkcję

function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; }

Aby użyć just do

$ echo 'hello world' | regex1 'hello\s(.*)'
world
 11
Author: opsb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-09 06:37:31

Sugestia dla ciebie - możesz użyć rozszerzenia parametru, aby usunąć część nazwy z ostatniego podkreślenia i podobnie na początku:

f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}

Wtedy name będzie miała wartość abc.

Zobacz Apple developer docs , wyszukaj "Parameter Expansion".

 2
Author: martin clayton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-12 01:16:46

Jeśli masz Basha, możesz użyć rozszerzonego globbingu

shopt -s extglob
shopt -s nullglob
shopt -s nocaseglob
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

Lub

ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done
 1
Author: ghostdog74,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-12 04:12:25