Dize ayırıcıya göre ayır ve N-th öğesini al


75

Bir dizgem var:

one_two_three_four_five

Bir değişken kaydetmek gerekir Adeğeri twove değişken içinde Bdeğer fouryukarıdaki dizesinden

Yanıtlar:


106

Kullanım cutile _alan ayırıcı olarak ve istenen alanları olsun:

A="$(cut -d'_' -f2 <<<'one_two_three_four_five')"
B="$(cut -d'_' -f4 <<<'one_two_three_four_five')"

Ayrıca echoHere string yerine pipet kullanabilir ve kullanabilirsiniz :

A="$(echo 'one_two_three_four_five' | cut -d'_' -f2)"
B="$(echo 'one_two_three_four_five' | cut -d'_' -f4)"

Örnek:

$ s='one_two_three_four_five'

$ A="$(cut -d'_' -f2 <<<"$s")"
$ echo "$A"
two

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four

Alternatif var mı? Ksh (bsh değil) kullanıyorum ve ksh: sözdizimi hatası veriyor: `<'beklenmeyen
Alex

@Alex Düzenlemelerimi kontrol et.
heemayl

Güzel cevap, küçük bir sorum var: "$ s" değişkeniniz bir yol klasörü ise ne olur? Bir yol klasörü kesmeye çalıştığımda aşağıdakileri yapmaktan hoşlanıyorum: `$ FILE = my_user / my_folder / [file] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* Burada neler olduğunu biliyor musunuz?
Henry Navarro

1
Ve sadece son alanı istiyorsanız, sadece kabuk yerleşiklerini kullanarak - konumunu belirtmek zorunda kalmadan ya da alan sayısını bilmediğinizde:echo "${s##*_}"
Amit Naidu

19

Yalnızca POSIX sh yapılarını kullanarak , bir seferde bir sınırlayıcıyı ayrıştırmak için parametre değiştirme yapılarını kullanabilirsiniz . Bu kodun gerekli sayıda alan olduğunu varsaydığını, aksi takdirde son alanın tekrarlandığını unutmayın.

string='one_two_three_four_five'
remainder="$string"
first="${remainder%%_*}"; remainder="${remainder#*_}"
second="${remainder%%_*}"; remainder="${remainder#*_}"
third="${remainder%%_*}"; remainder="${remainder#*_}"
fourth="${remainder%%_*}"; remainder="${remainder#*_}"

Alternatif olarak, joker genişlemesi devre dışı bırakılmış ve IFSsınırlayıcı karakterine ayarlanmış olarak işaretsiz bir parametre değiştirme kullanabilirsiniz (sınırlayıcı tek boşluklu olmayan tek bir karakter ise veya herhangi bir boşluk dizisi sınırlayıcıysa işe yarar).

string='one_two_three_four_five'
set -f; IFS='_'
set -- $string
second=$2; fourth=$4
set +f; unset IFS

Bu konumsal parametreleri gizler. Bunu bir fonksiyonda yaparsanız, sadece fonksiyonun konumsal parametreleri etkilenir.

Yine bir başka yaklaşım, readyerleşimi kullanmaktır .

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF

Kullanımı varsayılana unset IFSdönmüyor IFS. Bundan sonra birisi OldIFS="$IFS"OldIFS içinde boş bir değere sahip olacaktır. Ayrıca, önceki IFS değerinin varsayılan olduğu varsayılmaktadır, ki bu mümkün değildir (ve faydalıdır). Tek doğru çözüm, old="$IFS"IFS = "$ old" ile depolamak ve daha sonra geri yüklemek. Veya ... bir alt kabuk kullanın (...). Ya da daha iyisi, cevabımı oku.
sorontar

@ sorontar varsayılan değere unset IFSgeri yüklemez IFS, ancak alan efektini varsayılan efekte döndürür. Evet, bu bir sınırlama, ancak pratikte genellikle kabul edilebilir bir sınırdır. Alt kabuktaki problem, veriyi çıkarmamız gerektiğidir. Sonunda devleti değiştirmeyen bir çözüm gösteriyorum read. (POSIX kabukları içinde çalışır, ancak IIRC Bourne kabuğunda değil çünkü readburada-belge nedeniyle bir alt kabukta çalıştırılır .) <<<Cevabınızdaki gibi kullanmak sadece ksh / bash / zsh içinde çalışan bir değişkendir.
Gilles

Bir deniz kabuğu hakkında att ya da yadigarı kabuk ile bile bir sorun görmüyorum. Test edilen tüm kabuklar (eski burne dahil) ana kabukta doğru değeri sağlar.
sorontar

Benim yolum bir şeyse ne olacak user/my_folder/[this_is_my_file]*? Bu adımları takip ederken elde ettiğim şey[this_is_my_file]*
Henry Navarro

@HenryNavarro Bu çıkış, cevabımdaki kod parçacıklarından hiçbiriyle uyuşmuyor. Hiçbiri özel bir şey yapmıyor /.
Gilles

17

Bir awkcevap görmek istedim, işte bir tane:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')

1
Ve eğer son parçayı istiyorsanız - konumunu belirtmek zorunda kalmadan ya da alan sayısını bilmediğinizde:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit Naidu

8

En basit yol (<<< ile kabukları için):

 IFS='_' read -r a second a fourth a <<<"$string"

Bir kabuk şikayet ettiğinden $abunun yerine geçici bir değişken kullanmak $_.

Tam bir komut dosyasında:

 string='one_two_three_four_five'
 IFS='_' read -r a second a fourth a <<<"$string"
 echo "$second $fourth"

IFS değişmiyor, sorun set -fyok (Pathname genişletme) Konumsal parametrelerde değişiklik yok ("$ @").


IFS'yi değiştirmeden tüm mermilere (evet, tüm POSIX dahil) taşınabilir bir çözüm için veya set -f(biraz daha karmaşık) heredoc eşdeğerini kullanın:

string='one_two_three_four_five'

IFS='_' read -r a second a fourth a <<-_EOF_
$string
_EOF_

echo "$second $fourth"

Bu çözümlerin (hem here-doc hem de kullanımın <<<tüm takip eden yeni satırları kaldıracağını anlayın .
Ve bunun "bir liner" değişken içeriğine göre tasarlandığını anlayın .
Çok liner'ler için çözümler mümkündür, ancak daha karmaşık yapılara ihtiyaç vardır.


Bash 4.4 sürümünde çok basit bir çözüm mümkün

readarray -d _ -t arr <<<"$string"

echo "array ${arr[1]} ${arr[3]}"   # array numbers are zero based.

POSIX kabukları diziler olmadığından, POSIX kabukları için eşdeğer yoktur.

Dizileri olan kabukları için basit olabilir:
(attsh, lksh, mksh, ksh ve bash çalıştığı test edilmiştir)

set -f; IFS=_; arr=($string)

Ancak değişkenleri ve seçenekleri saklamak ve sıfırlamak için birçok ek tesisat ile:

string='one_* *_three_four_five'

case $- in
    *f*) noglobset=true; ;;
    *) noglobset=false;;
esac

oldIFS="$IFS"

set -f; IFS=_; arr=($string)

if $noglobset; then set -f; else set +f; fi

echo "two=${arr[1]} four=${arr[3]}"

Zsh'de, diziler 1'de başlar ve dizgiyi varsayılan olarak bölmez.
Bu yüzden, bu çalışmayı zsh ile yapmak için bazı değişiklikler yapılması gerekiyor.


read OP, 76. ve 127. elementleri uzun bir dizeden çıkarmak istemediği sürece kullanılan çözümler basit ...
don_crissti 26:16

@don_crissti Evet, elbette, fakat benzer bir yapı: readarraybu durum için kullanımı daha kolay olabilir.
sorontar

@ don_crissti Ayrıca dizileri olan kabukları için bir dizi çözüm ekledi. POSIX kabukları için, dizileri olmayan, 127 öğeye kadar konumsal parametreler herhangi bir önlemle "basit" bir çözüm değildir.
sorontar

2

Sizinle zshdizgiyi (on _) bir diziye bölebilirsiniz :

elements=(${(s:_:)string})

ve daha sonra dizi indeksi aracılığıyla her bir öğeye erişin:

print -r ${elements[4]}

İçinde unutmayın zsh(aksine ksh/ bash) dizi indeksleri 1 başlayacak .


Lütfen set -filk çözüme uyarı eklemeyi unutmayın . ... *belki yıldız olabilir ?
sorontar

@sorontar - neden ihtiyacım olduğunu düşünüyorsunuz set -f? read/ Kullanmıyorum IFS. Çözümlerimi bir dize *_*_*ya da başka bir şeyle deneyin ...
don_crissti 26:16

Zsh için değil, ancak kullanıcı bir ksh çözümü istedi, bu yüzden o kabuğunda kullanmaya çalışabilir. Bir uyarı, sorunu önlemesine yardımcı olur.
sorontar

1

Bir piton çözeltisine izin verilir mi?

# python -c "import sys; print sys.argv[1].split('_')[1]" one_two_three_four_five
two

# python -c "import sys; print sys.argv[1].split('_')[3]" one_two_three_four_five
four

No kötü bad answet
Raj Kumar

0

Başka bir awk örneği; anlaşılması daha kolay.

A=\`echo one_two_three_four_five | awk -F_ '{print $1}'\`  
B=\`echo one_two_three_four_five | awk -F_ '{print $2}'\`  
C=\`echo one_two_three_four_five | awk -F_ '{print $3}'\`  
... and so on...  

Değişkenlerle de kullanılabilir.
Farz edin:
this_str = "one_two_three_four_five"
Sonra aşağıdakiler işe yarar :
A = `echo $ {this_str} | awk -F_ '{print $ 1}' `
B =` echo $ {this_str} | awk -F_ '{print $ 2}' `
C =` echo $ {this_str} | awk -F_ '{3 $ yazdırın}' `
... ve diğerleri ...

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.