Subtekenreeks extraheren in Bash

Gegeven een bestandsnaam in de vorm someletters_12345_moreleters.ext, wil ik de 5 cijfers extraheren en in een variabele stoppen.

Dus om het punt te benadrukken, ik heb een bestandsnaam met een x aantal tekens, dan een reeks van vijf cijfers, omringd door een enkel onderstrepingsteken aan weerszijden en dan nog een set van x aantal tekens. Ik wil het 5-cijferige nummer nemen en dat in een variabele zetten.

Ik ben erg geïnteresseerd in het aantal verschillende manieren waarop dit kan worden bereikt.


Antwoord 1, autoriteit 100%

Gebruik knippen:

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

Meer algemeen:

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

Antwoord 2, autoriteit 90%

Als aconstant is, voert de volgende parameteruitbreiding substringextractie uit:

b=${a:12:5}

waarbij 12de offset is (op nul gebaseerd) en 5de lengte is

Als de onderstrepingstekens rond de cijfers de enige zijn in de invoer, kunt u het voor- en achtervoegsel (respectievelijk) in twee stappen verwijderen:

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

Als er andere onderstrepingstekens zijn, is het waarschijnlijk hoe dan ook haalbaar, zij het lastiger. Als iemand weet hoe beide uitbreidingen in een enkele uitdrukking kunnen worden uitgevoerd, zou ik dat ook graag willen weten.

Beide gepresenteerde oplossingen zijn pure bash, zonder spawning, dus erg snel.


Antwoord 3, Autoriteit 13%

Generieke oplossing waarbij het nummer overal in de bestandsnaam kan zijn, met behulp van de eerste van dergelijke sequenties:

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

Een andere oplossing om exact een deel van een variabele te extraheren:

number=${filename:offset:length}

Als uw bestandsnaam altijd het formaat heeft stuff_digits_...U kunt AWK gebruiken:

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

Nog een andere oplossing om alles te verwijderen, behalve cijfers, gebruik

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

Antwoord 4, Autoriteit 13%

Probeer gewoon cut -c startIndx-stopIndx


Antwoord 5, Autoriteit 5%

Hier is hoe ik het zou doen:

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

Uitleg:

bash-specifiek:

Regelmatige uitdrukkingen (RE): _([[:digit:]]{5})_

  • _BENT LITERALEN OM TE DEMMEN / ANKER MATCHING Grenzen voor de snaar die is gekoppeld
  • ()Maak een Capture Group
  • [[:digit:]]is een tekenklasse, ik denk dat het voor zichzelf spreekt
  • {5}betekent precies vijf van het eerdere karakter, klasse (zoals in dit voorbeeld) of een groep moet overeenkomen met

In het Engels, kunt u eraan bedenken dat deze zich zo gedraagt: de FNString is geopenbaard karakter door teken totdat we een _zien op welk punt de Capture Group geopend en we proberen vijf cijfers te evenaren. Als dat matching succesvol is op dit punt, bespaart de Capture Group de vijf cijfers die doorkruist. Als het volgende teken een _is, is de voorwaarde succesvol, de Capture Group wordt beschikbaar in BASH_REMATCH, en de volgende NUM=Verklaring uitvoeren. Als een deel van de matching mislukt, zijn opgeslagen details weggegooid en het karakter door de verwerking van het teken gaat verder na de _. b.v. Indien FNwaar _1 _12 _123 _1234 _12345_, zijn er vier valse starts voordat deze een overeenkomst heeft gevonden.


Antwoord 6, Autoriteit 5%

In het geval dat iemand meer rigoureuze informatie wil, kunt u het ook doorzoeken in Man Bash zoals deze

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

Resultaat:

$ {parameter: offset}
    $ {parameter: offset: lengte}
       Expansie substring. Breidt uit tot maximaal lengte tekens van
       Parameter vanaf het teken opgegeven door offset. Indien
       Lengte is weggelaten, breidt uit naar de substring van parameter start- 
       op het teken gespecificeerd door offset. lengte en offset zijn:
       rekenkundige uitdrukkingen (zie ARITHMETIC EVALUATION hieronder). Indien
       offset evalueert tot een getal kleiner dan nul, de waarde wordt gebruikt
       als een offset vanaf het einde van de waarde van de parameter. Rekenkundig
       uitdrukkingen die beginnen met een - moeten worden gescheiden door witruimte
       van het voorgaande : te onderscheiden van de Use Default
       Uitbreiding van waarden. Als lengte resulteert in een getal kleiner dan
       nul, en parameter is niet @ en niet geïndexeerd of associatief
       array, wordt het geïnterpreteerd als een offset vanaf het einde van de waarde
       parameter in plaats van een aantal tekens, en de expan‐
       sion zijn de tekens tussen de twee offsets. Als parameter is
       @, het resultaat is lengte positionele parameters beginnend bij off‐
       set. Als parameter een geïndexeerde arraynaam is, gesubscript door @ of
       *, het resultaat is de lengte van de leden van de array die begint met
       ${parameter[offset]}. Er wordt een negatieve offset genomen ten opzichte van
       één groter dan de maximale index van de opgegeven array. Sub-
       tekenreeksuitbreiding toegepast op een associatieve array produceert unde‐
       boete resultaten. Merk op dat een negatieve offset moet worden gescheiden
       van de dubbele punt met ten minste één spatie om verwarring te voorkomen
       met de :- uitbreiding. Indexering van subtekenreeksen is op nul gebaseerd, tenzij
       de positionele parameters worden gebruikt, in welk geval de indexering
       begint standaard bij 1. Als offset 0 is, en de positionele
       parameters worden gebruikt, wordt $0 voorafgegaan door de lijst.

Antwoord 7, autoriteit 3%

Het verbaast me dat deze pure bash-oplossing niet is bedacht:

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

U wilt waarschijnlijk IFS resetten naar de waarde die het eerder was, of unset IFSdaarna!


Antwoord 8, autoriteit 2%

Voortbouwend op het antwoord van jor (wat niet werkt voor mij):

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

Antwoord 9, autoriteit 2%

Als we ons concentreren op het concept van:
“Een reeks van (een of meerdere) cijfers”

We kunnen verschillende externe tools gebruiken om de getallen te extraheren.
We kunnen vrij gemakkelijk alle andere tekens wissen, ofwel sed of tr:

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

Maar als $name meerdere reeksen getallen bevat, zal het bovenstaande mislukken:

Als “name=someletters_12345_moreleters_323_end.ext”, dan:

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

We moeten reguliere expressies (regex) gebruiken.
Om alleen de eerste run (12345 niet 323) in sed en perl te selecteren:

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

Maar we kunnen het net zo goed direct in bash(1)doen:

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

Hierdoor kunnen we de EERSTE reeks cijfers van elke lengte extraheren
omgeven door andere tekst/tekens.

Opmerking: regex=[^0-9]*([0-9]{5,5}).*$;komt slechts exact overeen met 5 cijfers loopt. 🙂

(1): sneller dan het aanroepen van een externe tool voor elke korte tekst. Niet sneller dan alle verwerking binnen sed of awk voor grote bestanden.


Antwoord 10

Voldoet aan de vereisten

Ik heb een bestandsnaam met x aantal tekens en vervolgens vijf cijfers
reeks omgeven door een enkel onderstrepingsteken aan weerszijden en dan een andere
set van x aantal tekens. Ik wil het 5-cijferige nummer nemen en
zet dat in een variabele.

Ik heb enkele grepmanieren gevonden die nuttig kunnen zijn:

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

of beter

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

En dan met -Posyntaxis:

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

Of als je het precies in 5 tekens wilt laten passen:

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

Ten slotte, om het in een variabele op te slaan, hoeft u alleen maar de syntaxis var=$(command)te gebruiken.


Antwoord 11

Zonder subprocessen kunt u:

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

Een zeer kleine variant hiervan werkt ook in ksh93.


Antwoord 12

Hier is een prefix-suffix-oplossing (vergelijkbaar met de oplossingen van JB en Darron) die overeenkomt met het eerste blok cijfers en niet afhankelijk is van de omringende underscores:

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

Antwoord 13

Ik ben dol op het vermogen van sedom met regex-groepen om te gaan:

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

Een iets algemenere optie zou zijn om nietaan te nemen dat je een onderstrepingsteken _hebt dat het begin van je cijferreeks markeert, en dus bijvoorbeeld alle niet-nummers verwijdert je krijgt voor je reeks: 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.

Meer hierover, voor het geval je niet al te zeker bent van regexps:

  • sis voor _s_ubstitute
  • [0-9]+komt overeen met 1+ cijfers
  • \1linkt naar groep n.1 van de regex-uitvoer (groep 0 is de hele match, groep 1 is in dit geval de match tussen haakjes)
  • pvlag is voor _p_rinting

Alle escapes \zijn er om de regexp-verwerking van sedte laten werken.


Antwoord 14

Mijn antwoord heeft meer controle over wat je uit je string wilt halen. Hier is de code over hoe je 12345uit je string kunt extraheren

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

Dit is efficiënter als je iets wilt extraheren met tekens zoals abcof speciale tekens zoals _of -. Bijvoorbeeld: als je string zo is en je wilt alles wat na someletters_en voor _moreleters.extstaat:

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

Met mijn code kun je aangeven wat je precies wilt.
Uitleg:

#*Het verwijdert de voorgaande string inclusief de bijbehorende sleutel. Hier is de sleutel die we noemden _
%Het zal de volgende string inclusief de bijbehorende sleutel verwijderen. Hier is de sleutel die we noemden ‘_more*’

Doe zelf wat experimenten en je zou dit interessant vinden.


Antwoord 15

Gezien test.txt is een bestand met “ABCDEFGHIJKLMNOPQRSTUVWXYZ”

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

Antwoord 16

shell cut – print een specifiek bereik van karakters of een bepaald deel van een string

#methode1) met bash

str=2020-08-08T07:40:00.000Z
 echo ${str:11:8}

#methode2) met knippen

str=2020-08-08T07:40:00.000Z
 cut -c12-19 <<< $str

#method3) bij het werken met awk

str=2020-08-08T07:40:00.000Z
 awk '{time=gensub(/.{11}(.{8}).*/,"\\1","g",$1); print time}' <<< $str

Antwoord 17

vergelijkbaar met substr(‘abcdefg’, 2-1, 3) in php:

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

Antwoord 18

Ok, hier gaat pure parametervervanging met een lege string. Voorbehoud is dat ik sommige lettersen meerlettersheb gedefinieerd als alleen tekens. Als ze alfanumeriek zijn, werkt dit niet zoals het is.

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

Antwoord 19

Er is ook het bash-ingebouwde ‘expr’-commando:

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

Antwoord 20

Een beetje laat, maar ik kwam dit probleem net tegen en vond het volgende:

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

Ik heb het gebruikt om milliseconden resolutie te krijgen op een ingebed systeem dat geen %N voor datum heeft:

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

Antwoord 21

Een bash-oplossing:

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

Hiermee wordt een variabele met de naam xgekloond. De var xkan worden gewijzigd in de var _.

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

Antwoord 22

Inclusief einde, vergelijkbaar met JS- en Java-implementaties. Verwijder +1 als je dit niet wenst.

function substring() {
    local str="$1" start="${2}" end="${3}"
    if [[ "$start" == "" ]]; then start="0"; fi
    if [[ "$end"   == "" ]]; then end="${#str}"; fi
    local length="((${end}-${start}+1))"
    echo "${str:${start}:${length}}"
} 

Voorbeeld:

   substring 01234 0
    01234
    substring 012345 0
    012345
    substring 012345 0 0
    0
    substring 012345 1 1
    1
    substring 012345 1 2
    12
    substring 012345 0 1
    01
    substring 012345 0 2
    012
    substring 012345 0 3
    0123
    substring 012345 0 4
    01234
    substring 012345 0 5
    012345

Meer voorbeeldoproepen:

   substring 012345 0
    012345
    substring 012345 1
    12345
    substring 012345 2
    2345
    substring 012345 3
    345
    substring 012345 4
    45
    substring 012345 5
    5
    substring 012345 6
    substring 012345 3 5
    345
    substring 012345 3 4
    34
    substring 012345 2 4
    234
    substring 012345 1 3
    123

Other episodes