Bash'de alt dizeyi çıkarın


728

Formda bir dosya adı verildiğinde someletters_12345_moreleters.ext, 5 haneyi çıkarmak ve bunları bir değişkene koymak istiyorum.

Bu noktayı vurgulamak için, x karakterli bir dosya adım var, sonra her iki tarafta tek bir alt çizgi ile çevrili beş basamaklı bir sıra, sonra başka bir x karakter kümesi var. 5 basamaklı sayıyı alıp bir değişkene koymak istiyorum.

Bunun gerçekleştirilebileceği farklı yolların sayısıyla çok ilgileniyorum.


5
JB'nin cevabı açıkça oyları kazanıyor - kabul edilen cevabı değiştirme zamanı?
Jeff

3
Cevapların çoğu sorunuza cevap vermiyor gibi görünüyor çünkü soru belirsiz. Msgstr "x karakterli bir dosya ismim var, sonra her iki tarafta tek bir alt çizgi ve daha sonra başka bir x karakter kümesi ile çevrili beş haneli bir sıra var" . Bu tanım gereği abc_12345_def_67890_ghi_defgeçerli bir girdidir. Ne olmak istiyorsun? Diyelim ki sadece bir tane 5 haneli sıra var. Hala var abc_def_12345_ghi_jklya 1234567_12345_1234567veya 12345d_12345_12345egeçerli giriş olarak girdi tanımına dayalı ve cevapları çoğu aşağıda Kaldıramayacaksan.
gman

2
Bu sorunun çok spesifik bir örnek girişi var. Bu nedenle, bu özel durum için çok sayıda özel cevap aldı (sadece rakamlar, aynı _sınırlayıcı, hedef dizeyi yalnızca bir kez içeren giriş vb.). En iyi (en genel ve en hızlı) cevap , 10 yıl sonra sadece 7 yukarı oy alırken, diğer sınırlı cevapların yüzlerce yanıtı vardır. Geliştiricilere olan inancımı kaybettiriyor
Dan

Yanıtlar:


692

Kesim kullanın :

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

Daha genel:

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

1
daha genel cevap tam olarak aradığım şey, teşekkürler
Berek Bryan

71
-F bayrağı, bir programcının kullanacağı 0 tabanlı dizinler yerine 1 tabanlı dizinler alır.
Matthew G

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

3
echoDeğişkenlerin düzensiz boşluk veya kabuk metakarakterleri içeremeyeceğinden emin değilseniz , bağımsız değişkenler etrafında çift tırnak kullanmalısınız . Daha fazla bilgi için stackoverflow.com/questions/10067266/…
üçlü

'-F' den sonraki '2' sayısı, kabuğa alt dizinin 2. kümesini çıkarmasını bildirir.
Sandun

1087

Eğer x sabiti, ekstraksiyon substring Aşağıdaki parametre genişletme gerçekleştirir olup:

b=${a:12:5}

burada 12 ofsettir (sıfır temelli) ve 5 uzunluktur

Rakamların etrafındaki alt çizgiler girişte yalnızca alt çizgilerse, ön ek ve son eki (sırasıyla) iki adımda kaldırabilirsiniz:

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

Başka alt çizgiler varsa, muhtemelen daha zor olsa da, muhtemelen mümkündür. Biri her iki genişletmeyi tek bir ifadede nasıl gerçekleştireceğini bilirse, ben de bilmek istiyorum.

Sunulan her iki çözüm de saf bastır, hiçbir süreç yumurtlama söz konusu değildir, dolayısıyla çok hızlıdır.


18
@SpencerRathbun bash: ${${a#*_}%_*}: bad substitutionbenim GNU bash 4.2.45.
JB.

2
@jonnyB, Geçmişte bir süre işe yaradı. İş arkadaşlarım tarafından durdurulduğu söylendi ve bir sed komutu ya da bir şey olarak değiştirdiler. Tarihe baktığımda sh, muhtemelen çizgi olan bir senaryoda çalıştırıyordum . Bu noktada artık çalışamıyorum.
Spencer Rathbun

22
JB, "12" nin ofset (sıfır temelli) ve "5" in uzunluk olduğunu açıklığa kavuşturmalısınız. Ayrıca, @gontard'ın bağlantısını düzenleyen +1!
Doktor J

1
Bunu bir komut dosyasında "sh run.sh" olarak çalıştırırken, Hatalı Değiştirme hatası alabilirsiniz. Bundan kaçınmak için run.sh (chmod + x run.sh) izinlerini değiştirin ve komut dosyasını "./run.sh" olarak çalıştırın
Ankur

2
Ofset parametresi de negatif olabilir, BTW. Kolonun üzerine yapıştırmamaya dikkat etmeniz gerekir, aksi takdirde bash bunu :-“Varsayılan Değerleri Kullan” yerine koyar. Böylece ${a: -12:5}5 karakter sondan 12 karakter ${a: -12:-5}ve son 12 ve son 5 arasında 7 karakter verir.
JB.

97

Bu dizilerin ilkini kullanarak sayının dosya adında herhangi bir yerde olabileceği genel çözüm:

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

Bir değişkenin tam olarak bir kısmını çıkarmak için başka bir çözüm:

number=${filename:offset:length}

Dosya adınız her zaman formata sahipse stuff_digits_...awk kullanabilirsiniz:

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

Rakamlar hariç her şeyi kaldırmak için başka bir çözüm kullanın

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

2
Dosyanın son satırından rakam / kelimeyi çıkarmak istersem ne olur?
Bir Sahra

93

sadece kullanmaya çalış cut -c startIndx-stopIndx


2
StartIndex-lastIndex - 1 gibi bir şey var mı?
Niklas

1
@Niklas In bash, proly startIndx-$((lastIndx-1))
kahverengi.2179

3
start=5;stop=9; echo "the rain in spain" | cut -c $start-$(($stop-1))
kahverengi.2179

1
Sorun, girişin dinamik olması, çünkü boruyu temelde almak için de kullandığım için. git log --oneline | head -1 | cut -c 9-(end -1)
Niklas

line=Git log olarak iki parçaya ayrılırsa bu kesim ile yapılabilir - oneline | kafa -1`&& echo $ hattı | $ (($ {# satır} -1)) 'fakat bu özel durumda, daha iyi olabilir kullanmak için - kesik 9-c sed olarakgit log --oneline | head -1 | sed -e 's/^[a-z0-9]* //g'
brown.2179

34

Birinin daha titiz bilgi istemesi durumunda, bunu man bash'da da bu şekilde arayabilirsiniz

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

Sonuç:

$ {Parametre: offset}
       $ {Parametre: kaydırma uzunluğu}
              Substring Genişlemesi. Uzunluğuna kadar karakterleri genişletir
              offset tarafından belirtilen karakterden başlayan parametre. Eğer
              uzunluk atlanır, parametre başlangıcının alt dizesine genişler‐
              ofset tarafından belirtilen karakterde. uzunluk ve ofset
              aritmetik ifadeler (aşağıdaki ARİTMETİK DEĞERLENDİRMEYE bakınız). Eğer
              ofset sıfırdan küçük bir sayı olarak değerlendirilir, değer kullanılır
              parametrenin değerinin sonundan öteleme olarak. Aritmetik
              a ile başlayan ifadeler boşlukla ayrılmalıdır
              öncekinden: Kullanım Varsayılanından ayırt edilmek
              Değerlerin genişlemesi. Uzunluk, daha küçük bir sayı olarak değerlendirilirse
              sıfır, ve parametre @ değil, dizinlenmiş veya ilişkilendirilebilir değil
              dizisi, değerin sonundan öteleme olarak yorumlanır
              bir dizi karakter yerine parametrenin
              sion, iki ofset arasındaki karakterdir. Parametre
              @, sonuç kapalıdan başlayarak uzunluk konumsal parametreler‐
              Ayarlamak. Parametre @ veya tarafından abone olunan dizinlenmiş bir dizi adı ise veya
              *, sonuç diziyle başlayan dizinin uzunluk üyeleridir
              $ {Parametre [konum]}. Negatif bir ofset,
              belirtilen dizinin maksimum dizininden bir daha büyük. Alt-
              ilişkilendirilebilir bir diziye uygulanan dize genişletmesi unde‐ üretir
              para cezası. Negatif bir ofsetin ayrılması gerektiğini unutmayın
              karışmamak için kolondan en az bir boşluk
              ile: - genişleme. Alt dize dizine ekleme sıfır tabanlı değildir
              konumsal parametreler kullanılır, bu durumda indeksleme
              varsayılan olarak 1'de başlar. Uzaklık 0 ise ve konumsal
              parametreleri kullanıldığında, listeye $ 0 öneki eklenir.

2
Yukarıda belirtildiği gibi negatif değerlere sahip çok önemli bir uyarı: a - ile başlayan aritmetik ifadeler, öncekinden boşlukla ayrılmalıdır: Varsayılan Değerleri Kullan genişlemesinden ayırmak için. Bir ${var: -4}
varlığın

26

İşte nasıl yaparım:

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

Açıklama:

Bash özgü:

Normal İfadeler (RE): _([[:digit:]]{5})_

  • _ eşleştirilen dizenin sınırlarını sınırlamak / sabitlemek için değişmez değerler
  • () bir yakalama grubu oluştur
  • [[:digit:]] bir karakter sınıfı, bence kendisi için konuşuyor
  • {5} önceki karakterin, sınıfın (bu örnekte olduğu gibi) veya grubun tam olarak beşi eşleşmelidir

İngilizce olarak, bunun böyle davrandığını düşünebilirsiniz: yakalama grubunun hangi noktada açıldığınıFN görene ve karakter beş basamakla eşleşmeye çalışana kadar dize karakter karakter yinelenir . Bu eşleme bu noktaya kadar başarılı olursa, yakalama grubu çaprazlanan beş basamağı kaydeder. Bir sonraki karakter bir ise, koşul başarılı olur, yakalama grubu içinde kullanılabilir hale gelir ve sonraki ifade çalıştırılabilir. Eşleşmenin herhangi bir bölümü başarısız olursa, kaydedilen ayrıntılar atılır ve karakter işleme göre karakter . örneğin nerede olursa , bir eşleşme bulmadan önce dört yanlış başlangıç ​​olur.__BASH_REMATCHNUM=_FN_1 _12 _123 _1234 _12345_


3
Bu benim yaptığım gibi birden fazla şeyi ayıklamanız gerekse bile çalışan genel bir yöntemdir.
zebediah49

3
Bu gerçekten de en genel cevaptır ve kabul edilmelidir. Düzenli bir ifade için çalışır, yalnızca sabit bir konumda veya aynı sınırlayıcı arasında (etkinleştirir cut) bir karakter dizisi değil . Ayrıca harici bir komutun yürütülmesine bağlı değildir.
Dan Dascalescu

1
Bu cevap cezalandırılmamış.
chepner

Bu harika! Durumum için farklı başlatma / durdurma dilimetreleri (_ yerine) ve değişken uzunluk sayılarını (. {5} için) kullanacak şekilde uyarladım. Birisi bu kara büyüyü parçalayıp açıklayabilir mi?
Paul

1
@Paul Cevabıma daha fazla ayrıntı ekledim. Umarım yardımcı olur.
nicerobot

21

Bu saf bash çözümünün gelmediğine şaşırdım:

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

Muhtemelen IFS'yi daha önce veya unset IFSdaha sonra hangi değere sıfırlamak istiyorsunuz !


1
saf bash çözümü değil, saf kabukta çalıştığını düşünüyorum (/ bin / sh)
kayn

5
+1 IFSIFS=_ read -r _ digs _ <<< "$a"; echo "$digs"
Ayarlanmamış

2
Bu, yol adı genişletmesine tabidir! (bu yüzden kırıldı).
gniourf_gniourf

20

Jor'in cevabına dayanarak (bu benim için işe yaramaz):

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

12
Düzenli İfadeler karmaşık bir şeye sahip olduğunuzda gerçek bir anlaşmadır ve sadece alt çizgileri saymak olmaz cut.
Aleksandr Levchuk

12

Gereksinimleri takip

Ben x karakter sayısı bir dosya adı sonra her iki tarafında tek bir alt çizgi sonra x karakter sayısı başka bir dizi çevrili beş haneli bir sıra var. 5 basamaklı sayıyı alıp bir değişkene koymak istiyorum.

grepYararlı olabilecek bazı yollar buldum :

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

ya da daha iyisi

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

Ve sonra -Posözdizimi ile:

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

Veya tam olarak 5 karaktere sığdırmak istiyorsanız:

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

Son olarak, bir değişkende saklanması için var=$(command)sözdiziminin kullanılması yeterlidir .


2
Bugünlerde egrep kullanmaya gerek komut kendisi sizi uyarır, olduğuna inanıyoruz: Invocation as 'egrep' is deprecated; use 'grep -E' instead. Cevabınızı düzenledim.
Nörotransmitter

11

Eğer
"(bir veya birkaç) basamaklı bir sayı" kavramına odaklanırsak

Sayıları çıkarmak için birkaç harici araç kullanabiliriz.
Sed veya tr gibi diğer tüm karakterleri kolayca silebiliriz:

name='someletters_12345_moreleters.ext'

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

Ancak $ name birkaç sayı dizisi içeriyorsa, yukarıdakiler başarısız olur:

"Name = someletters_12345_moreleters_323_end.ext" ise:

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

Düzenli ifadeler kullanmalıyız (normal ifade).
Sed ve perl'de yalnızca ilk çalıştırmayı (12345 323 değil) seçmek için:

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

Ama bunu doğrudan bash'da da yapabiliriz (1) :

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

Bu,
başka herhangi bir metin / karakterle çevrelenmiş herhangi bir uzunluktaki İLK basamak sayısını çıkarmamızı sağlar .

Not : regex=[^0-9]*([0-9]{5,5}).*$;sadece 5 basamaklı koşularla eşleşir. :-)

(1) : her kısa metin için harici bir araç çağırmaktan daha hızlıdır. Büyük dosyalar için sed veya awk içindeki tüm işlemleri yapmaktan daha hızlı değil.


10

Herhangi bir alt işlem olmadan şunları yapabilirsiniz:

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

Bunun çok küçük bir çeşidi de ksh93'te çalışacaktır.


9

İlk basamak bloğuyla eşleşen ve çevredeki alt çizgilere bağlı olmayan bir önek sonek çözümü (JB ve Darron tarafından verilen çözümlere benzer):

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

7

sedNormal gruplarla başa çıkma yeteneğini seviyorum :

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

Biraz daha genel bir seçenek olacaktır değil bir çizgi olduğunu varsaymak _dolayısıyla sıranızdaki önce olsun olmayan tüm sayılar kapalı sıyırma mesela senin basamak dizisinin başlangıcını işaret: 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.

Normal ifadelerden çok emin değilseniz, daha fazlası:

  • s _s_ubstitute için
  • [0-9]+ 1+ rakamla eşleşir
  • \1 normal ifade çıktısının 1. grubuna bağlantılar (grup 0 tüm eşleşmedir, grup 1 bu durumda parantez içindeki eşleşmedir)
  • p bayrak _p_rinting içindir

Tüm kaçışlar regexp işleme çalışması \yapmak için vardır sed.


6

Cevabım dizenizden ne istediğiniz üzerinde daha fazla kontrole sahip olacak. İşte 12345dizenizden nasıl çıkarabileceğinize dair kod

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

Eğer gibi herhangi bir karakter olan bir şey çıkarmak istiyorsanız bu daha verimli olacak abcya da benzeri herhangi bir özel karakterleri _veya -. Örneğin: Dizeniz böyle ise someletters_ve sonrasında ve öncesinde olan her şeyi istiyorsanız _moreleters.ext:

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

Kodumla tam olarak ne istediğinizi anlatabilirsiniz. Açıklama:

#*Eşleşen anahtar da dahil olmak üzere önceki dizeyi kaldıracaktır. Burada bahsettiğimiz _ %anahtar, eşleşen anahtarı içeren aşağıdaki dizeyi kaldıracağıdır. Burada bahsettiğimiz anahtar '_more *'

Bazı deneyler kendiniz yapın ve bunu ilginç bulacaksınız.


6

Verilen test.txt "ABCDEFGHIJKLMNOPQRSTUVWXYZ" içeren bir dosyadır

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

Bu söz konusu girdiye son derece spesifiktir. Genel soruya (OP'nin sorması gereken) tek genel çözüm bir normal ifade kullanmaktır .
Dan Dascalescu

3

Tamam, boş bir dize ile saf Parametre Değiştirme gidiyor. Uyarı, bazı harfleri ve moreleleri sadece karakter olarak tanımladığımdır . Alfasayısal ise, bu olduğu gibi çalışmaz.

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

2
harika ama en azından bash v4 gerektirir
olibre

2

php substr ('abcdefg', 2-1, 3) benzeri:

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

Bu, bu girdiye son derece özgüdür. Genel soruya (OP'nin sorması gereken) tek genel çözüm bir normal ifade kullanmaktır .
Dan Dascalescu

1

Ayrıca bash yerleşik 'expr' komutu da var:

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

4
exprbir yerleşik değildir.
gniourf_gniourf

1
=~Desteklediği operatörün ışığı altında da gerekli değildir [[.
chepner

1

Biraz geç, ama ben sadece bu problemle karşılaştım ve aşağıdakileri buldum:

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

Tarih için% N olmayan gömülü bir sistemde milisaniye çözünürlük elde etmek için kullandım:

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

1

Bir bash çözümü:

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

Bu, adlı bir değişkeni hızlandıracaktır x. Var var xile değiştirilebilir _.

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

1

Mürekkep içeren son, JS ve Java uygulamalarına benzer. Bunu istemiyorsanız + 1'i kaldırın.

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}}"
} 

Misal:

    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

Daha fazla örnek çağrı:

    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

Rica ederim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.