Wie finde ich mit grep die Position eines Charakters?

10

Ich muss die Position eines Zeichens in einer Zeichenfolge mit dem Befehl grep identifizieren.

Beispiel ist die Zeichenfolge RAMSITALSKHMAN|1223333.

grep -n '[^a-zA-Z0-9\$\~\%\#\^]'

Wie finde ich die Position |in der angegebenen Zeichenfolge?

user82782
quelle
muss es mit grep sein?
Braiam

Antworten:

27

Mit können -bSie den Byte-Offset ermitteln, der der Position für einfachen Text entspricht (jedoch nicht für UTF-8 oder ähnliches).

$ echo "RAMSITALSKHMAN|1223333" | grep -aob '|'
14:|

Oben benutze ich den -aSchalter, um grep anzuweisen, die Eingabe als Text zu verwenden. erforderlich, wenn Binärdateien bearbeitet werden und der -oSchalter nur die übereinstimmenden Zeichen ausgibt.

Wenn Sie nur die Position möchten, können Sie grep verwenden, um nur die Position zu extrahieren:

$ echo "RAMSITALSKHMAN|1223333" | grep -aob '|' | grep -oE '[0-9]+'
14

Wenn Sie eine seltsame Ausgabe erhalten, prüfen Sie, ob in grep Farben aktiviert sind. Sie können Farben deaktivieren, indem Sie --colors=neveran grep übergeben oder dem Befehl grep ein \(das alle Aliase deaktiviert) voranstellen. Beispiel:

$ echo "RAMSITALSKHMAN|1223333" | grep -aob '|' --color=never | \grep -oE '^[0-9]+'
14

Für eine Zeichenfolge, die mehrere Übereinstimmungen zurückgibt, leiten head -n1Sie die Pipe durch , um die erste Übereinstimmung zu erhalten.

Beachten Sie, dass ich oben beides verwende und dass letzteres nicht funktioniert, wenn grep über eine ausführbare Datei (Skript oder auf andere Weise) "aliasiert" ist, nur wenn Aliase verwendet werden.

runejuhl
quelle
3
Suchen Sie jetzt nach 2;)
Izkata
Danke @Izkata, du hast recht. Ich habe meinen Beitrag ein wenig aktualisiert und den fehlenden Hut hinzugefügt ^:)
runejuhl
1
Welche Version von grep hast du benutzt? Ich erhalte 0:|als output-- weil 0 die Byte-Position des Zeilenanfangs |ist, an der gefunden wird.
Alex
@ Alex GNU grep aus Debian Stretch: grep (GNU grep) 2.27. Verwenden Sie vielleicht OS X?
Runejuhl
11

Versuchen:

printf '%s\n' 'RAMSITALSKHMAN|1223333.' | grep -o . | grep -n '|'

Ausgabe:

15:|

Dies gibt Ihnen die Position mit Index-1.

cuonglm
quelle
Es funktioniert nicht :(
user82782
1
@ user82782: Welchen Befehl haben Sie ausgeführt? Woher weißt du, dass es nicht funktioniert hat?
2.
printf '%s\n' '|' | grep -o . | grep -n '|'druckt 1nicht 0wie erwartet.
l0b0
1
@ l0b0: Das OP sagt nicht, dass er die Indexbasis 0 oder 1 haben wollte.
cuonglm
Ich meine nur, was ein Softwareentwickler erwarten würde.
l0b0
8

Wenn Sie die Shell verwenden, können Sie rein integrierte Operationen verwenden, ohne externe Prozesse wie oder :

$ str="RAMSITALSKHMAN|1223333"
$ tmp="${str%%|*}"
$ if [ "$tmp" != "$str" ]; then
> echo ${#tmp}
> fi
14
$ 

Hierbei wird eine Parametererweiterung verwendet , um alle Vorkommen von |Folgen durch eine beliebige Zeichenfolge zu entfernen und diese in einer temporären Variablen zu speichern. Es geht dann nur noch darum, die Länge der temporären Variablen zu messen, um den Index von zu erhalten |.

Beachten Sie, dass ifüberprüft wird, ob das |überhaupt in der ursprünglichen Zeichenfolge vorhanden ist. Ist dies nicht der Fall, entspricht die temporäre Variable dem Original.

Beachten Sie auch, dass dies den auf Null basierenden Index liefert, der |im Allgemeinen beim Indizieren von Bash-Strings nützlich ist. Wenn Sie jedoch einen einseitigen Index benötigen, können Sie dies tun:

$ echo $((${#tmp}+1))
15
$ 
Digitales Trauma
quelle
1
Wahrscheinlich die beste Antwort, diese Syntax ist wunderschön und so schnell und einfach zu verwenden, wenn Sie ihre Bedeutung verstehen, es lebe der Kern
vdegenne
4

Sie können die awk- indexFunktion verwenden, um die Position in Zeichen zurückzugeben, an der die Übereinstimmung auftritt:

echo "RAMSITALSKHMAN|1223333"|awk 'END{print index($0,"|")}'
15

Wenn es Ihnen nichts ausmacht, die Perl- indexFunktion zu verwenden, werden keine, ein oder mehrere Vorkommen eines Zeichens gemeldet:

echo "|abc|xyz|123456|zzz|" | \
perl -nle '$pos=-1;while (($off=index($_,"|",$pos))>=0) {print $off;$pos=$off+1}'

Nur aus Gründen der Lesbarkeit wurde die Pipeline auf zwei Zeilen aufgeteilt.

Solange das Zielzeichen gefunden wird, wird indexein positiver Wert basierend auf Null (0) zurückgegeben. Daher ist die Zeichenfolge "abc | xyz | 123456 | zzz |" Beim Parsen werden die Positionen 0, 4, 8, 15 und 19 zurückgegeben.

JRFerguson
quelle
Für diese Verwendung ist awk nützlicher / einfacher als grep.
Archemar
Dies gibt nur die erste Position aus und funktioniert nicht mit Zeichenfolgen wieRAMSITALSKHMAN|1|223333
cuonglm
3

Wir können es auch mit "expr match" oder "expr index" machen

Ausdruck stimmt mit $ string $ substring überein, wobei $ substring eine RE ist.

echo `expr match "RAMSITALSKHMAN|1223333" '[A-Z]*.|'`

Und oben geben Sie die Position an, da sie die Länge der übereinstimmenden Teilzeichenfolge zurückgibt.

Genauer gesagt für den Suchindex:

mystring="RAMSITALSKHMAN|122333"
echo `expr index "$mystring" '|'`
Bluefoggy
quelle
Ich habe nicht genug Ruf, um irgendwo anders zu kommentieren. Ich persönlich mochte die Antwort von @Gnouc. Warum aber awk verwenden und es komplexer machen, wenn wir mit 'expr'
bluefoggy
@ Kingsdeb es ist nur ein Vorschlag.
Avinash Raj
@kingsdeb: Weil (1) die awkLösungen trivial modifiziert werden können, um diese Informationen in jeder Zeile einer Datei zu melden (alles, was Sie tun müssen, ist das END, was nie wirklich nötig war, aus JRFergusons Antwort zu entfernen , und Avinash Rajs tut es bereits) ; exprUm dies mit der Lösung zu tun, müssten Sie eine explizite Schleife hinzufügen (und die Antwort von Gnouc ist, wie ich sehen kann, nicht leicht anpassbar, um dies überhaupt zu tun), und (2) die awkLösungen können angepasst werden, um alle zu melden passt in jeder Zeile etwas leichter als die exprLösung (tatsächlich macht Avinash Rajs das auch schon).
G-Man
Warum würden Sie echo `...`hier verwenden?
Stéphane Chazelas
Dies soll nur die Ausgabe hier zeigen
bluefoggy
2

Noch ein awk Befehl ,

$ echo 'RAMSITALSKHMAN|1223333'| awk 'BEGIN{ FS = "" }{for(i=1;i<=NF;i++){if($i=="|"){print i;}}}'
15

Indem Sie das Feldtrennzeichen als Nullzeichenfolge festlegen, wandelt awk einzelne Zeichen im Datensatz als separate Felder um.

Avinash Raj
quelle
2

Einige Alternativen sind:

Ähnlich wie Gnoucs Antwort, aber mit der Shell:

echo 'RAMSITALSKHMAN|1223333' |
tr -c \| \\n | 
sh

sh: line 15: syntax error near unexpected token `|
sh: line 15: `|'

mit sedund dcmöglicherweise über mehrere Zeilen:

echo 'RAMSITALSKHMAN|1223333' |
sed 's/[^|]/1+/g;s/|/p/;1i0 1+' |dc

15

mit $IFS...

IFS=\|; set -f; set -- ${0+RAMSITALSKHMAN|1223333}; echo $((${#1}+1))

Das wird dir auch sagen, wie viele es sind wie ...

echo $(($#-1))
mikeserv
quelle