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.
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.
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: -)
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.
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"
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
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".
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
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