Bir dizeyi bash kabuğunda en az bir boşlukla ayrılmış birden çok dizeye nasıl bölebilirim?


224

Her ikisi arasında en az bir boşluk ile birçok kelime içeren bir dize var. Dizeyi tek tek kelimelere nasıl bölebilirim, böylece bunlar arasında geçiş yapabilirim?

Dize bağımsız değişken olarak geçirilir. Örn ${2} == "cat cat file". Nasıl döngüde bulunabilirim?

Ayrıca, bir dizenin boşluk içerip içermediğini nasıl kontrol edebilirim?


1
Ne tür bir kabuk? Bash, cmd.exe, powershell ...?
Alexey Sviridov

Sadece döngüye mi ihtiyacınız var (örneğin, kelimelerin her biri için bir komut yürütmek)? Veya daha sonra kullanmak üzere bir kelime listesi saklamanız mı gerekiyor?
DVK

Yanıtlar:


281

String değişkenini bir fordöngüye geçirmeyi denediniz mi? Bash, birincisi, boşlukta otomatik olarak bölünecek.

sentence="This is   a sentence."
for word in $sentence
do
    echo $word
done

 

This
is
a
sentence.

1
@MobRule - Bunun tek dezavantajı, daha fazla işlem için çıktıyı kolayca yakalayamamanız (en azından bir şekilde hatırlamıyorum).
STDOUT'a bir

4
Sadece bir değişkene ekler olabilir: A=${A}${word}).
Lucas Jones

1
$ text [[bu kelimeleri 1 $, 2 $, 3 $ ... vb. içine koyacaktır]
Rajesh

32
Aslında bu hile sadece yanlış bir çözüm değil, aynı zamanda kabuk globbing nedeniyle de son derece tehlikelidir . beklenen yerine touch NOPE; var='* a *'; for a in $var; do echo "[$a]"; doneçıktılar (okunabilirlik için LF'ler SPC ile değiştirilir). [NOPE] [a] [NOPE][*] [a] [*]
Tino

@mob Dizeyi belirli bir dizeye göre bölmek istersem ne yapmalıyım? örnek ".xlsx" ayırıcısı.

296

Tek tek öğelere erişebilmek için bir diziye dönüştürmeyi seviyorum:

sentence="this is a story"
stringarray=($sentence)

artık tek tek öğelere doğrudan erişebilirsiniz (0 ile başlar):

echo ${stringarray[0]}

veya döngüye girmek için dizeye geri dönüştürebilirsiniz:

for i in "${stringarray[@]}"
do
  :
  # do whatever on $i
done

Elbette dize üzerinden döngü yapmak daha önce cevaplanmıştı, ancak bu cevap daha sonra kullanılmak üzere bireysel elemanları takip etmeme dezavantajına sahipti:

for i in $sentence
do
  :
  # do whatever on $i
done

Ayrıca bkz . Bash Array Referansı .


26
Ne yazık ki mükemmel değil, çünkü kabuk-globbing: beklenen yerine touch NOPE; var='* a *'; arr=($var); set | grep ^arr=çıktılararr=([0]="NOPE" [1]="a" [2]="NOPE")arr=([0]="*" [1]="a" [2]="*")
Tino

@Tino: globbing'in müdahale etmesini istemiyorsanız, kapatın. Çözüm daha sonra joker karakterlerle de iyi çalışacaktır. Bence en iyi yaklaşım bu.
Alexandros

3
@Alexandros Benim yaklaşımım, yalnızca varsayılan olarak güvenli olan ve her bağlamda mükemmel çalışan kalıpları kullanmaktır. Güvenli bir çözüm elde etmek için kabuk küreciliğini değiştirme gereksinimi çok tehlikeli bir yoldan daha fazlasıdır, zaten karanlık taraftır. Bu yüzden tavsiyem asla burada böyle bir desen kullanmaya alışmamaktır, çünkü er ya da geç bazı ayrıntıları unutacaksınız ve sonra birileri hatalarınızdan yararlanır. Basında bu tür istismarlara dair kanıt bulabilirsiniz. Her. Tek. Gün.
Tino

86

Sadece yerleşik "set" kabuklarını kullanın. Örneğin,

$ text ayarla

Bundan sonra, $ text içindeki tek tek kelimeler $ 1, $ 2, $ 3 vb. Olacaktır. Sağlamlık için, genellikle

set - önemsiz $ metin
vardiya

$ metninin boş olduğu durumu ele almak veya kısa çizgiyle başlamak için. Örneğin:

text = "Bu bir testtir"
set - önemsiz $ metin
vardiya
kelime için; yapmak
  echo "[$ word]"
tamam

Bu yazdırır

[Bu]
[dır-dir]
[A]
[Ölçek]

5
Bu, bireysel parçalara doğrudan erişilebilmesi için, varı bölmenin mükemmel bir yoludur. + 1; sorunumu çözdü
Cheekysoft

Kullanmayı önerecektim awkama setçok daha kolay. Artık bir setfanboyum. Teşekkürler @Idelic!
Yzmir Ramirez

22
Bu tür şeyleri yaparsanız lütfen kabuk küreciliğinin farkında olun: beklenen yerine touch NOPE; var='* a *'; set -- $var; for a; do echo "[$a]"; doneçıktılar . Sadece bölünmüş dizede SHELL metakarakterlerinin% 101 olduğundan eminseniz kullanın! [NOPE] [a] [NOPE][*] [a] [*]
Tino

4
@Tino: Bu sorun sadece burada değil, her yerde geçerlidir, ancak bu durumda globbing'i devre dışı bırakmak için hemen set -fönce set -- $varve set +fsonra yapabilirsiniz.
İdelic

3
@Idelic: İyi yakaladın. İle set -fçözümünüz de güvenlidir. Ancak set +fher kabuğun varsayılanıdır, bu yüzden dikkat edilmesi gereken önemli bir detaydır, çünkü diğerleri muhtemelen farkında değildir (ben de olduğum gibi).
Tino

81

BASH 3 ve üstü için muhtemelen en kolay ve en güvenli yol:

var="string    to  split"
read -ra arr <<<"$var"

( arrdizenin bölünmüş bölümlerini alan dizi nerede ) veya girişte yeni satırlar varsa ve yalnızca ilk satırdan daha fazlasını istiyorsanız:

var="string    to  split"
read -ra arr -d '' <<<"$var"

(içindeki boşluğa dikkat edin, -d ''bırakılamaz), ancak bu size beklenmedik bir yeni satır verebilir <<<"$var"(bu, sonuçta bir LF eklediğinden).

Misal:

touch NOPE
var="* a  *"
read -ra arr <<<"$var"
for a in "${arr[@]}"; do echo "[$a]"; done

Beklenen çıktılar

[*]
[a]
[*]

çünkü bu çözüm (önceki tüm çözümlerin aksine) beklenmedik ve çoğu zaman kontrol edilemeyen mermi küreciliğine yatkın değildir.

Ayrıca bu, muhtemelen istediğiniz gibi IFS'nin tam gücünü verir:

Misal:

IFS=: read -ra arr < <(grep "^$USER:" /etc/passwd)
for a in "${arr[@]}"; do echo "[$a]"; done

Gibi bir şey çıktılar:

[tino]
[x]
[1000]
[1000]
[Valentin Hilbig]
[/home/tino]
[/bin/bash]

Gördüğünüz gibi, alanlar da bu şekilde korunabilir:

IFS=: read -ra arr <<<' split  :   this    '
for a in "${arr[@]}"; do echo "[$a]"; done

çıktılar

[ split  ]
[   this    ]

IFSBASH'de kullanımın kendi başına bir konu olduğunu lütfen unutmayın , bu yüzden testleriniz, bununla ilgili bazı ilginç konular:

  • unset IFS: SPC, TAB, NL ve on line başlangıç ​​ve bitiş işlemlerini yok sayar
  • IFS='': Alan ayırma yok, sadece her şeyi okur
  • IFS=' ': SPC çalıştırmaları (ve yalnızca SPC)

Son bir örnek

var=$'\n\nthis is\n\n\na test\n\n'
IFS=$'\n' read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

çıktılar

1 [this is]
2 [a test]

süre

unset IFS
var=$'\n\nthis is\n\n\na test\n\n'
read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

çıktılar

1 [this]
2 [is]
3 [a]
4 [test]

BTW:

  • Alışık değilseniz $'ANSI-ESCAPED-STRING'buna alışması, bu bir zaman kazandıran bu.

  • İçerir -r(içinde olduğu gibi read -a arr <<<"$var") yoksa okuma ters eğik çizgi kaçar. Bu okuyucu için bir egzersiz olarak bırakılmıştır.


İkinci soru için:

Bir dizesinde şey için teste Genellikle sopa case: (Eğer fallthrough kullanım multiplce gerekiyorsa, dava sadece ilk maçı yürütür not bu defada birden çok durum için kontrol edebilirsiniz gibi case(kelime oyununu ifadeleri) ve bu ihtiyaç oldukça sık bir durumdur ) tasarlanmıştır:

case "$var" in
'')                empty_var;;                # variable is empty
*' '*)             have_space "$var";;        # have SPC
*[[:space:]]*)     have_whitespace "$var";;   # have whitespaces like TAB
*[^-+.,A-Za-z0-9]*) have_nonalnum "$var";;    # non-alphanum-chars found
*[-+.,]*)          have_punctuation "$var";;  # some punctuation chars found
*)                 default_case "$var";;      # if all above does not match
esac

Böylece, SPC'yi kontrol etmek için dönüş değerini aşağıdaki gibi ayarlayabilirsiniz:

case "$var" in (*' '*) true;; (*) false;; esac

Neden case? Normal regex sekanslarından biraz daha okunabilir olduğundan ve Shell metakarakterleri sayesinde tüm ihtiyaçların% 99'unu çok iyi işler.


2
Bu cevap, vurgulanan globbing sorunları ve kapsamlılığı nedeniyle daha fazla oyu hak ediyor
Brian Agnew

@brian Teşekkürler. Kabuk metakarakterlerinin bu bağlamda artık zarar vermeyecek şekilde globbing'i kullanabileceğinizi set -fveya set -o noglobdeğiştirebileceğinizi lütfen unutmayın . Ama ben gerçekten bunun bir arkadaşı değilim, çünkü bu kabuğun çok gücünü geride bırakıyor / bu ayarı ileri geri değiştirmeye çok meyilli.
Tino

2
Harika cevap, gerçekten daha fazla oyu hak ediyor. Davanın düşmesiyle ilgili yan not - bunu ;&başarmak için kullanabilirsiniz . Hangi bash sürümünde göründüğünden emin değilim. Ben 4.3 kullanıcısı
Sergiy Kolodyazhnyy

2
@Bunu henüz bilmediğim için not ettiğiniz için teşekkürler! Bu yüzden baktım, Bash4'te ortaya çıktı . ;&C'de olduğu gibi patern kontrolü olmayan zorla düşme. Ve ;;&daha başka patern kontrolleri yapmaya devam eden de var. Yani ;;gibidir if ..; then ..; else if ..ve ;;&gibidir if ..; then ..; fi; if .., nerede ;&gibi olduğu m=false; if ..; then ..; m=:; fi; if $m || ..; then ..- one (başkalarından) öğrenme asla durmaz;)
Tino

@Tino Bu kesinlikle doğrudur - öğrenme sürekli bir süreçtir. Aslında, ;;&yorum yapmadan önce bilmiyordum : D Teşekkürler ve kabuk sizinle olabilir;)
Sergiy Kolodyazhnyy

43
$ echo "This is   a sentence." | tr -s " " "\012"
This
is
a
sentence.

Boşlukları kontrol etmek için grep kullanın:

$ echo "This is   a sentence." | grep " " > /dev/null
$ echo $?
0
$ echo "Thisisasentence." | grep " " > /dev/null     
$ echo $?
1

1
In BASH echo "X" |genellikle ile değiştirilebilir <<<"X", bunun gibi: grep -s " " <<<"This contains SPC". Bunun echo X | read varaksine bir şey yaparsanız farkı fark edebilirsiniz read var <<< X. Sadece ikinci varkabuk değişkenini geçerli kabuğa alırken, ilk varyantta erişmek için şu şekilde gruplandırmalısınız:echo X | { read var; handle "$var"; }
Tino

17

(A) Bir cümleyi kelimelerine (boşlukla ayrılmış) bölmek için şunu kullanarak varsayılan IFS'yi kullanabilirsiniz:

array=( $string )


Aşağıdaki snippet'i çalıştırma örneği

#!/bin/bash

sentence="this is the \"sentence\"   'you' want to split"
words=( $sentence )

len="${#words[@]}"
echo "words counted: $len"

printf "%s\n" "${words[@]}" ## print array

çıktı olacak

words counted: 8
this
is
the
"sentence"
'you'
want
to
split

Gördüğünüz gibi, tek veya çift tırnakları da sorunsuzca kullanabilirsiniz

Notlar:
- Bu temelde mafya cevabının aynısıdır , ancak bu şekilde diziyi daha fazla ihtiyaç için saklarsınız. Sadece tek bir döngüye ihtiyacınız varsa, bir satır daha kısa olan cevabını kullanabilirsiniz :)
- bir dizeyi ayırıcıya göre bölmek için alternatif yöntemler için lütfen bu soruya bakın .


(B) Bir dizedeki bir karakteri kontrol etmek için normal bir ifade eşleşmesi de kullanabilirsiniz.
Kullanabileceğiniz boşluk karakterinin varlığını kontrol etmek için örnek:

regex='\s{1,}'
if [[ "$sentence" =~ $regex ]]
    then
        echo "Space here!";
fi

Normal ifade ipucu (B) için +1, ancak yanlış çözelti (A) için -1, kabuk yuvarlamaya eğilimli bir hatadır. ;)
Tino

6

Sadece bash ile boşlukları kontrol etmek için:

[[ "$str" = "${str% *}" ]] && echo "no spaces" || echo "has spaces"

1
echo $WORDS | xargs -n1 echo

Bu, her kelimeyi çıktılar, daha sonra uygun gördüğünüz gibi listeyi işleyebilirsiniz.

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.