web-dev-qa-db-de.com

Teilstring in Bash extrahieren

Bei einem Dateinamen in der Form someletters_12345_moreleters.ext möchte ich die 5 Ziffern extrahieren und in eine Variable einfügen.

Um den Punkt zu unterstreichen, habe ich einen Dateinamen mit x Anzahl von Zeichen, dann eine fünfstellige Sequenz, die auf beiden Seiten von einem einzelnen Unterstrich umgeben ist, und dann eine andere Menge von x Anzahl von Zeichen. Ich möchte die 5-stellige Zahl nehmen und das in eine Variable setzen.

Ich bin sehr interessiert an den verschiedenen Möglichkeiten, wie dies erreicht werden kann.

650
Berek Bryan

Verwenden Sie cut :

echo 'someletters_12345_moreleters.ext' | cut -d'_' -f 2

Generischer:

INPUT='someletters_12345_moreleters.ext'
SUBSTRING=$(echo $INPUT| cut -d'_' -f 2)
echo $SUBSTRING
621
FerranB

Wenn x konstant ist, führt die folgende Parametererweiterung eine Teilstring-Extraktion durch:

b=${a:12:5}

dabei ist 12 der Offset (nullbasiert) und 5 die Länge

Wenn die Unterstriche um die Ziffern die einzigen in der Eingabe sind, können Sie das Präfix und das Suffix in zwei Schritten entfernen:

tmp=${a#*_}   # remove prefix ending in "_"
b=${tmp%_*}   # remove suffix starting with "_"

Wenn es andere Unterstriche gibt, ist es wahrscheinlich sowieso machbar, wenn auch schwieriger. Wenn jemand weiß, wie man beide Erweiterungen in einem einzigen Ausdruck ausführt, würde ich es gerne wissen.

Beide vorgestellten Lösungen sind reine Bash-Lösungen, ohne dass Prozesse entstehen, daher sehr schnell.

990
JB.

Generische Lösung, bei der die Nummer an einer beliebigen Stelle im Dateinamen stehen kann, wobei die erste der folgenden Sequenzen verwendet wird:

number=$(echo $filename | egrep -o '[[:digit:]]{5}' | head -n1)

Eine andere Lösung, um genau einen Teil einer Variablen zu extrahieren:

number=${filename:offset:length}

Wenn Ihr Dateiname immer das Format stuff_digits_... hat, können Sie awk verwenden:

number=$(echo $filename | awk -F _ '{ print $2 }')

Noch eine andere Lösung, um alles außer Ziffern zu entfernen, verwenden

number=$(echo $filename | tr -cd '[[:digit:]]')

versuche einfach cut -c startIndx-stopIndx zu benutzen

82
brown.2179

Für den Fall, dass jemand strengere Informationen haben möchte, können Sie diese auch in man bash wie folgt durchsuchen

$ man bash [press return key]
/substring  [press return key]
[press "n" key]
[press "n" key]
[press "n" key]
[press "n" key]

Ergebnis:

 $ {Parameter: Offset} 
 $ {Parameter: Offset: Länge} 
 Teilstringerweiterung. Wird auf Zeichen der Länge des Parameters 
 Erweitert, beginnend mit dem durch offset angegebenen Zeichen. Wenn die Länge 
 Weggelassen wird, wird die Teilzeichenfolge des Parameters um das durch offset angegebene Zeichen erweitert. Länge und Offset sind 
 arithmetische Ausdrücke (siehe ARITHMETISCHE BEWERTUNG unten). Wenn der Versatz 
 Eine Zahl kleiner als Null ergibt, wird der Wert 
 Als Versatz vom Ende des Werts des Parameters verwendet. Arithmetische 
 Ausdrücke, die mit einem - beginnen, müssen durch ein Leerzeichen 
 Vom vorhergehenden getrennt werden, um von der Erweiterung Use Default 
 Values ​​unterschieden zu werden. Wenn die Länge eine Zahl kleiner als 
 Null ergibt und der Parameter nicht @ und kein indiziertes oder assoziatives 
 Array ist, wird er als Versatz vom Ende des Werts 
 Interpretiert. Die Erweiterung 
 ist das Zeichen zwischen den beiden Offsets. Wenn der Parameter 
 @ Ist, beginnen die Längenpositionsparameter mit off - 
 Set. Wenn parameter ein indizierter Arrayname ist, der mit @ oder 
 * Subskribiert ist, sind das Ergebnis die Längenelemente des Arrays, die mit 
 $ {Parameter [offset]} beginnen. Ein negativer Versatz wird relativ zu 
 Einem Versatz genommen, der größer als der maximale Index des angegebenen Arrays ist. Auf ein assoziatives Array angewendete Sub - 
 - Zeichenfolgenerweiterung führt zu ungenauen 
 - Ergebnissen. Beachten Sie, dass ein negativer Versatz 
 Vom Doppelpunkt durch mindestens ein Leerzeichen getrennt sein muss, um zu vermeiden, dass 
 Mit der Erweiterung: - verwechselt wird. Die Teilstring-Indizierung basiert auf Null, sofern nicht 
 Die Positionsparameter verwendet werden. In diesem Fall beginnt die Indizierung 
 Standardmäßig bei 1. Wenn der Offset 0 ist und die Positionsparameter 
 Verwendet werden, wird der Liste $ 0 vorangestellt. 
33
jperelli

Aufbauend auf Jors Antwort (was bei mir nicht funktioniert):

substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
20
PEZ

Ich bin überrascht, dass diese reine Bash-Lösung nicht aufgetaucht ist:

a="someletters_12345_moreleters.ext"
IFS="_"
set $a
echo $2
# prints 12345

Sie möchten IFS wahrscheinlich auf den vorherigen Wert oder danach auf unset IFS zurücksetzen!

19
user1338062

So würde ich es machen:

FN=someletters_12345_moreleters.ext
[[ ${FN} =~ _([[:digit:]]{5})_ ]] && NUM=${BASH_REMATCH[1]}

Hinweis: Der obige Ausdruck ist ein regulärer Ausdruck und beschränkt sich auf Ihr spezielles Szenario mit fünf Ziffern, die von Unterstrichen umgeben sind. Ändern Sie den regulären Ausdruck, wenn Sie eine andere Übereinstimmung benötigen.

13
nicerobot

Befolgen Sie die Anforderungen

Ich habe einen Dateinamen mit x Zeichenanzahl, dann eine fünfstellige Folge, die auf beiden Seiten von einem einzelnen Unterstrich und dann von einer anderen Menge von x Zeichenanzahl umgeben ist. Ich möchte die 5-stellige Zahl nehmen und das in eine Variable setzen.

Ich habe einige grep Möglichkeiten gefunden, die nützlich sein können:

$ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]+" 
12345

oder besser

$ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]{5}" 
12345

Und dann mit -Po Syntax:

$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d+' 
12345

Oder wenn Sie möchten, dass es genau 5 Zeichen passt:

$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d{5}' 
12345

Um es schließlich in einer Variablen zu speichern, muss lediglich die Syntax var=$(command) verwendet werden.

12
fedorqui

Ohne Unterprozesse können Sie:

shopt -s extglob
front=${input%%_+([a-zA-Z]).*}
digits=${front##+([a-zA-Z])_}

Eine sehr kleine Variante davon wird auch in ksh93 funktionieren.

10
Darron

Wenn wir uns auf Folgendes konzentrieren:
"Eine Folge von (einer oder mehreren) Ziffern"

Wir könnten verschiedene externe Tools verwenden, um die Zahlen zu extrahieren.
Wir könnten ganz einfach alle anderen Zeichen löschen, entweder sed oder tr:

name='someletters_12345_moreleters.ext'

echo $name | sed 's/[^0-9]*//g'    # 12345
echo $name | tr -c -d 0-9          # 12345

Wenn $ name jedoch mehrere Zahlenreihen enthält, schlägt das oben Gesagte fehl:

Wenn "name = someletters_12345_moreleters_323_end.ext", dann:

echo $name | sed 's/[^0-9]*//g'    # 12345323
echo $name | tr -c -d 0-9          # 12345323

Wir müssen reguläre Ausdrücke (Regex) verwenden.
So wählen Sie nur den ersten Lauf (12345 nicht 323) in sed und Perl aus:

echo $name | sed 's/[^0-9]*\([0-9]\{1,\}\).*$/\1/'
Perl -e 'my $name='$name';my ($num)=$name=~/(\d+)/;print "$num\n";'

Aber wir könnten es auch direkt tun in Bash(1) :

regex=[^0-9]*([0-9]{1,}).*$; \
[[ $name =~ $regex ]] && echo ${BASH_REMATCH[1]}

Dies ermöglicht es uns, die ERSTE Folge von Ziffern beliebiger Länge zu extrahieren
umgeben von anderen Texten/Zeichen.

Anmerkung: regex=[^0-9]*([0-9]{5,5}).*$; stimmt nur mit genau 5 Ziffern überein. :-)

(1): schneller als für jeden Kurztext ein externes Tool aufzurufen. Nicht schneller als die gesamte Verarbeitung in sed oder awk für große Dateien.

10
user2350426

Hier ist eine Präfix-Suffix-Lösung (ähnlich den Lösungen von JB und Darron), die mit dem ersten Ziffernblock übereinstimmt und nicht von den umgebenden Unterstrichen abhängt:

str='someletters_12345_morele34ters.ext'
s1="${str#"${str%%[[:digit:]]*}"}"   # strip off non-digit prefix from str
s2="${s1%%[^[:digit:]]*}"            # strip off non-digit suffix from s1
echo "$s2"                           # 12345
9
codist

Given test.txt ist eine Datei, die "ABCDEFGHIJKLMNOPQRSTUVWXYZ" enthält.

cut -b19-20 test.txt > test1.txt # This will extract chars 19 & 20 "ST" 
while read -r; do;
> x=$REPLY
> done < test1.txt
echo $x
ST
6
Rick Osman

Ich mag die Fähigkeit von sed, mit Regex-Gruppen umzugehen:

> var="someletters_12345_moreletters.ext"
> digits=$( echo $var | sed "s/.*_\([0-9]\+\).*/\1/p" -n )
> echo $digits
12345

Eine etwas allgemeinere Option wäre nicht anzunehmen, dass Sie einen Unterstrich _ haben, der den Anfang Ihrer Ziffernfolge markiert, und daher zum Beispiel alle Nicht-Zahlen, die Sie vor Ihrer Sequenz erhalten, zu streichen: s/[^0-9]\+\([0-9]\+\).*/\1/p.


> man sed | grep s/regexp/replacement -A 2
s/regexp/replacement/
    Attempt to match regexp against the pattern space.  If successful, replace that portion matched with replacement.  The replacement may contain the special  character  &  to
    refer to that portion of the pattern space which matched, and the special escapes \1 through \9 to refer to the corresponding matching sub-expressions in the regexp.

Mehr dazu, falls Sie mit regulären Ausdrücken nicht allzu sicher sind:

  • s steht für _s_ubstitute
  • [0-9]+ entspricht 1+ Ziffern
  • \1 verweist auf die Gruppe n.1 der Regex-Ausgabe (Gruppe 0 ist die gesamte Übereinstimmung, Gruppe 1 ist in diesem Fall die Übereinstimmung in Klammern)
  • p Flag dient zum Drucken

Alle Escapezeichen \ sind dazu da, die Regexp-Verarbeitung von sed zum Laufen zu bringen.

6
Campa

Meine Antwort wird mehr Kontrolle darüber haben, was Sie von Ihrer Saite erwarten. Hier ist der Code, wie Sie 12345 aus Ihrer Zeichenfolge extrahieren können

str="someletters_12345_moreleters.ext"
str=${str#*_}
str=${str%_more*}
echo $str

Dies ist effizienter, wenn Sie etwas extrahieren möchten, das Zeichen wie abc oder Sonderzeichen wie _ oder - enthält. Zum Beispiel: Wenn Ihre Zeichenfolge so ist und Sie alles wollen, was nach someletters_ und vor _moreleters.ext steht:

str="someletters_123-45-24a&13b-1_moreleters.ext"

Mit meinem Code kannst du erwähnen, was genau du willst. Erläuterung:

#* Entfernt die vorhergehende Zeichenfolge einschließlich des passenden Schlüssels. Hier ist der Schlüssel, den wir erwähnt haben, _% Es wird die folgende Zeichenfolge einschließlich des passenden Schlüssels entfernt. Hier ist der Schlüssel, den wir erwähnt haben, '_more *'

Machen Sie einige Experimente selbst und Sie würden dies interessant finden.

Ok, hier geht die reine Parametersubstitution mit einem leeren String. Vorsichtsmaßnahme ist, dass ich Someletters und Moreletters nur als Zeichen definiert habe. Wenn sie alphanumerisch sind, funktioniert dies nicht wie es ist.

filename=someletters_12345_moreletters.ext
substring=${filename//@(+([a-z])_|_+([a-z]).*)}
echo $substring
12345
2
morbeo

ähnlich wie substr ('abcdefg', 2-1, 3) in php:

echo 'abcdefg'|tail -c +2|head -c 3
2
diyism

Es gibt auch den Bash-Befehl 'expr':

INPUT="someletters_12345_moreleters.ext"  
SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `  
echo $SUBSTRING
1
jor

Ein bisschen spät, aber ich bin gerade auf dieses Problem gestoßen und habe Folgendes festgestellt:

Host:/tmp$ asd=someletters_12345_moreleters.ext 
Host:/tmp$ echo `expr $asd : '.*_\(.*\)_'`
12345
Host:/tmp$ 

Ich habe es verwendet, um eine Millisekundenauflösung auf einem eingebetteten System zu erhalten, das nicht über% N für Datum verfügt:

set `grep "now at" /proc/timer_list`
nano=$3
fraction=`expr $nano : '.*\(...\)......'`
$debug nano is $nano, fraction is $fraction
1
russell

Eine bash Lösung:

IFS="_" read -r x digs x <<<'someletters_12345_moreleters.ext'

Dadurch wird eine Variable namens x gelöscht. Die Variable x kann in die Variable _ geändert werden.

input='someletters_12345_moreleters.ext'
IFS="_" read -r _ digs _ <<<"$input"
1
user2350426