Bash'de bir sınırlayıcıdaki bir dizeyi nasıl bölebilirim?


2041

Bir değişken depolanan bu dize var:

IN="bla@some.com;john@home.com"

Şimdi ben dizeleri ;ayırıcı tarafından bölmek istiyorum ki ben var:

ADDR1="bla@some.com"
ADDR2="john@home.com"

ADDR1Ve ADDR2değişkenlerine mutlaka ihtiyacım yok . Eğer daha iyi bir dizinin elemanları ise.


Aşağıdaki cevapların önerilerinden sonra, şu şekilde olduğum şeyle sonuçlandım:

#!/usr/bin/env bash

IN="bla@some.com;john@home.com"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

Çıktı:

> [bla@some.com]
> [john@home.com]

Internal_field_separator (IFS) olarak ayarlanmasıyla ilgili bir çözüm vardı ;. Bu cevapta ne olduğundan emin değilim, IFSvarsayılana nasıl sıfırlarsınız ?

RE: IFSçözüm, bunu denedim ve işe yarıyor, eski tutmak IFSve sonra geri yüklemek:

IN="bla@some.com;john@home.com"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

BTW, denediğimde

mails2=($IN)

Sadece ilk dizgiyi döngüde yazdırırken, etrafında köşeli parantez olmadan aldım $IN.


14
"Edit2" ile ilgili: Basitçe "IFS unset" yapabilirsiniz ve varsayılan durumuna geri dönecektir. Zaten varsayılan olmayan bir değere ayarlanmış olmasını beklemek için bir nedeniniz yoksa, açıkça kaydetmenize ve geri yüklemenize gerek yoktur. Dahası, bunu bir işlev içinde yapıyorsanız (ve değilseniz, neden olmasın?), IFS'yi yerel değişken olarak ayarlayabilirsiniz ve işlevden çıktığınızda önceki değerine dönecektir.
Brooks Moses

19
@BrooksMoses: (a) local IFS=...Mümkün olan yerlerde kullanmak için +1 ; (b) -1 için unset IFS, bu tam olarak IFS'yi varsayılan değerine sıfırlamaz, ancak ayarlanmamış bir IFS'nin varsayılan değer olan IFS ($ '\ t \ n') ile aynı şekilde davrandığına inanıyorum, ancak kötü uygulama kodunuzun hiçbir zaman IFS özel bir değere ayarlanmış olarak çağrılmayacağını varsaymak; (c) başka bir fikir, bir alt kabuğu çağırmaktır: (IFS=$custom; ...)alt kabuktan çıktığında IFS, orijinaline geri dönecektir.
dubiousjim

Sadece bir çalıştırılabilir nereye atmak karar yolları hızlı bir göz atmak istiyorum, bu yüzden çalıştırmak için başvurdu ruby -e "puts ENV.fetch('PATH').split(':')". Eğer saf bash kalmak istiyorsanız yardımcı olmaz ama yerleşik bir bölünme olan herhangi bir komut dosyası dili kullanmak daha kolaydır.
nicooga

4
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
user2037659

2
Bir dizi olarak kaydetmek için başka bir parantez kümesi koymak ve \nsadece bir boşluk değiştirmek zorunda kaldı. Yani son satır mails=($(echo $IN | tr ";" " ")). Şimdi mailsdizi gösterimini kullanarak mails[index]veya sadece bir döngüde yineleme kullanarak öğelerini kontrol edebilirsiniz
afranques

Yanıtlar:


1234

Dahili alan ayırıcı (IFS) değişkenini ayarlayabilir ve bir diziye ayrıştırmasına izin verebilirsiniz . Bu bir komutta gerçekleştiğinde, atama IFSyalnızca o komutun ortamına (to read) gerçekleşir. Daha sonra girdiyi, IFSdeğişken değerine göre bir diziye ayrıştırır ve bunu yineleyebiliriz.

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    # process "$i"
done

;Bir dizi öğeyi bir diziye iterek ayırarak ayrıştırır . $INHer biri bir satır girdi ile ayırarak tümünü işlemek için ;:

 while IFS=';' read -ra ADDR; do
      for i in "${ADDR[@]}"; do
          # process "$i"
      done
 done <<< "$IN"

22
Bu muhtemelen en iyi yoldur. IFS şu anki değerinde ne kadar kalacaktır, kodumu olmamak üzere ayarlanarak bozabilir ve işim bittiğinde bunu nasıl sıfırlayabilirim?
Chris Lutz

7
düzeltmenin uygulandıktan sonra, sadece okuma komutu süresince :)
Johannes Schaub - litb

14
Bir süre döngüsü kullanmadan her şeyi bir kerede okuyabilirsiniz: "# -d '' deki -r -d '' -a addr <<<" $ burada anahtardır, okumanın ilk satırda durmamasını söyler ( (-d varsayılan değerdir) ancak EOF veya NULL baytına kadar devam eder (yalnızca ikili verilerde görülür).
lhunath

55
@LucaBorrione Noktalı virgül ya da başka bir ayırıcı olmadan IFSaynı satırda ayarlama , readayrı bir komutun aksine, onu bu komutun kapsamına alır - böylece her zaman "geri yüklenir"; elle hiçbir şey yapmanıza gerek yoktur.
Charles Duffy

5
@imagineerBu yorum dizileri ve IFS'de tırnak içine alınması gereken yerel değişiklikler ile ilgili bir hata var $IN. Hata bash4.3'te düzeltildi .
chepner

973

Alındığı Bash kabuğu komut bölünmüş dizisi :

IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })

Açıklama:

Bu yapı tüm oluşumları değiştirir ';'(başlangıç //dizesinde araçlarla küresel değiştirin) INile ' ', daha sonra (tek bir boşluk) (en şey bu parantez yapmak) bir dizi olarak boşlukla sınırlandırılmış dize yorumlar.

Kıvrımlı parantezlerin içinde kullanılan ve her ';'karakteri bir ' 'karakterle değiştirmek için kullanılan sözdizimine Parameter Expansion ( Parametre Genişletme) adı verilir .

Bazı yaygın gotcha'lar var:

  1. Orijinal dizede boşluklar varsa, IFS kullanmanız gerekir :
    • IFS=':'; arrIN=($IN); unset IFS;
  2. Orijinal dizede boşluklar varsa ve ayırıcı yeni bir satırsa, IFS'yi aşağıdakilerle ayarlayabilirsiniz :
    • IFS=$'\n'; arrIN=($IN); unset IFS;

84
Sadece eklemek istiyorum: Bu en basit olanı, $ {arrIN [1]} (elbette sıfırlardan başlayarak) ile dizi öğelerine erişebilirsiniz
Oz123

26
Buldum: $ {} içindeki bir değişkeni değiştirme tekniği 'parametre genişletme' olarak bilinir.
KomodoDave

23
Hayır, bunun da boşluklar olduğunda işe yaradığını sanmıyorum ... ',' değerini '' dönüştürüyor ve sonra boşlukla ayrılmış bir dizi oluşturuyor.
Ethan

12
Çok özlü, ancak genel kullanım için uyarılar var : kabuk, diziye kelime bölünmesi ve genişletmeler uygular ; Sadece deneyin. IN="bla@some.com;john@home.com;*;broken apart". Kısacası: jetonlarınız gömülü alanlar ve / veya karakter içeriyorsa bu yaklaşım bozulur. *geçerli klasördeki dosya isimleri ile bir jeton eşleştirme yapmak gibi .
mklement0

53
Bu, başka nedenlerle kötü bir yaklaşımdır: Örneğin, dizeniz içeriyorsa ;*;, *geçerli dizindeki dosya adları listesine genişletilir. -1
Charles Duffy

249

Onları hemen işlemek sakıncası yoksa, bunu yapmak istiyorum:

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

Bir diziyi başlatmak için bu tür bir döngü kullanabilirsiniz, ancak muhtemelen bunu yapmanın daha kolay bir yolu vardır. Umarım bu yardımcı olur.


IFS cevabını saklamalıydın. Bana bilmediğim bir şey öğretti ve kesinlikle bir dizi yaptı, oysa bu sadece ucuz bir ikame yapıyor.
Chris Lutz

Anlıyorum. Evet bu saçma deneyleri yapıyorum, her şeyi cevaplamaya çalıştığımda yeni şeyler öğreneceğim. #Bash IRC geri bildirimlerine dayalı şeyleri düzenledim ve geri döndüm :)
Johannes Schaub - litb

33
-1, kelime bölüştürmenin farkında değilsiniz, çünkü kodunuza iki hata getiriyor. birincisi $ IN teklif etmediğinizde, diğeri ise bir satırsonu kelime ayırmada kullanılan tek sınırlayıcı gibi davrandığınız zamandır. IN içindeki her WORD üzerinde yineleniyorsunuz, her satırda değil, ve kesinlikle noktalı virgülle sınırlanmış her öğede değil, ancak işe yaramış gibi görünmenin yan etkisine sahip gibi görünebilir.
lhunath

3
"$ IN" yankısı olarak değiştirebilirsiniz | tr ';' '\ n' | okunurken -r ADDY; # $ "ADDY" işlemi yapın; onu şanslı hale getirmek için yapılır, sanırım :) Bu çatalı olacak ve döngü içinde dış değişkenleri değiştiremezsiniz (bu yüzden <<< "$ IN" sözdizimini kullandım)
Johannes Schaub - litb

8
Yorumlardaki tartışmayı özetlemek gerekirse: Genel kullanım için uyarılar : kabuk , dizeye sözcük bölünmesi ve genişletmeler uygular ; Sadece deneyin. IN="bla@some.com;john@home.com;*;broken apart". Kısacası: jetonlarınız gömülü alanlar ve / veya karakter içeriyorsa bu yaklaşım bozulur. *geçerli klasördeki dosya isimleri ile bir jeton eşleştirme yapmak gibi .
mklement0

202

Uyumlu cevap

Bunu yapmanın birçok farklı yolu var .

Bununla birlikte, ilk olarak bash, diğerlerinde çalışmayan birçok özel özelliğe (sözde bashisms ) sahip olduğunu not etmek önemlidir..

Özellikle, bu yazıdaki çözümlerde ve iplikteki diğerlerinde kullanılan diziler , ilişkilendirilebilir diziler ve kalıp ikamesi , bashislerdir ve birçok insanın kullandığı diğer kabuklar altında çalışmayabilir .

Mesela: Benim üzerinde Debian GNU / Linux , bir var standart denilen kabuk; Başka bir kabuk kullanmayı seven birçok insan tanıyorum; ve ayrıca özel bir araç var kendi kabuk yorumlayıcısıyla ().

İstenen dize

Yukarıdaki soruya bölünecek dize:

IN="bla@some.com;john@home.com"

Çözümümün diğer çözümleri kırabilecek boşluk içeren dizelere sağlam olmasını sağlamak için bu dizenin değiştirilmiş bir sürümünü kullanacağım:

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

Dizeyi ayırıcıya dayalı olarak ayır (sürüm> = 4.2)

Olarak saf bash , biz oluşturabilir dizi için geçici bir değer ile elemanlarının bölünmüş IFS ( giriş alanı ayırıcı ). IFS, diğer şeylerin yanı sıra, bashbir diziyi tanımlarken öğeler arasında hangi karakterlere sınırlayıcı olarak davranması gerektiğini söyler :

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS

bashKomutunun daha yeni sürümlerinde , bir komutun bir IFS tanımıyla öneklendirilmesi, yalnızca bu komutun IFS'sini değiştirir ve hemen ardından önceki değerine sıfırlar. Bu, yukarıdakileri yalnızca bir satırda yapabileceğimiz anlamına gelir:

IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'

Dizenin noktalı virgülle bölünmüş INadlı bir dizide saklandığını görebiliriz fields:

set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'

(Bu değişkenlerin içeriğini de kullanarak görüntüleyebiliriz declare -p:)

declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")

Bölme yapmanın readen hızlı yolunun bu olduğuna dikkat edin çünkü çağrılan çatal veya dış kaynak yoktur .

Dizi tanımlandıktan sonra, her alanı (veya dizideki şimdi tanımladığınız her öğeyi) işlemek için basit bir döngü kullanabilirsiniz:

# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
    echo "> [$x]"
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

Ya da benim gibi bir kaydırma yaklaşımı kullanarak işledikten sonra dizi her alanı bırakabilirsiniz :

while [ "$fields" ] ;do
    echo "> [$fields]"
    # slice the array 
    fields=("${fields[@]:1}")
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

Ve dizinin basit bir çıktısını almak istiyorsanız, bunun üzerinde döngü yapmanız bile gerekmez:

printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

Güncelleme: son > = 4.4

Yeni sürümlerinde, bashşu komutla da oynayabilirsiniz mapfile:

mapfile -td \; fields < <(printf "%s\0" "$IN")

Bu sözdizimi özel karakterleri, yeni satırları ve boş alanları korur!

Boş alanlar eklemek istemiyorsanız aşağıdakileri yapabilirsiniz:

mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}")   # drop '\n' added by '<<<'

İle mapfile, bir dizi bildirmeyi atlayabilir ve her birinde bir işlev çağırarak sınırlandırılmış öğeler üzerinde dolaylı olarak "döngü" oluşturabilirsiniz:

myPubliMail() {
    printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
    # mail -s "This is not a spam..." "$2" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail

(Not: \0Dizenin sonundaki boş alanları umursamıyorsanız veya yoksa, biçim dizesinin sonundaki işe yaramaz.)

mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail

# Seq:      0: Sending mail to 'bla@some.com', done.
# Seq:      1: Sending mail to 'john@home.com', done.
# Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

Veya kullanabilirsiniz <<<ve işlev gövdesinde eklediği satırsonu bırakmak için bazı işlemler içerir:

myPubliMail() {
    local seq=$1 dest="${2%$'\n'}"
    printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
    # mail -s "This is not a spam..." "$dest" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile <<<"$IN" -td \; -c 1 -C myPubliMail

# Renders the same output:
# Seq:      0: Sending mail to 'bla@some.com', done.
# Seq:      1: Sending mail to 'john@home.com', done.
# Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

Dizeyi ayırıcıya dayalı olarak ayır

Eğer kullanamıyorsanız bashsize yazma için birçok farklı kabuklar kullanılabilir şey istiyorsanız, ya, sık sık can not kullanmak bashisms - ve bu yukarıda çözümlerde kullanıyorum dizileri içerir.

Ancak, bir dizenin "öğeleri" üzerinde döngü oluşturmak için diziler kullanmamız gerekmez. Bir desenin ilk veya son oluşumundan bir dizenin alt dizelerini silmek için birçok kabukta kullanılan bir sözdizimi vardır . *Sıfır veya daha fazla karakter anlamına gelen joker karakter olduğunu unutmayın :

(Şimdiye kadar yayınlanan herhangi bir çözümde bu yaklaşımın olmaması, bu cevabı yazmamın ana nedenidir;)

${var#*SubStr}  # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*}  # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string

Score_Under tarafından açıklandığı gibi :

#ve sırasıyla dizenin başlangıcından ve sonundan% mümkün olan en kısa eşleşen alt dizeyi silin ve

##ve %%mümkün olan en uzun alt dizeyi silin.

Yukarıdaki sözdizimini kullanarak, alt dizeyi ayırıcıya kadar veya sonra silerek alt dizge "elemanları" dizeden ayıkladığımız bir yaklaşım oluşturabiliriz.

Aşağıdaki kod bloğu, (Mac OS'ler dahil bash),, , ve 'ler :

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
    # extract the substring from start of string up to delimiter.
    # this is the first "element" of the string.
    iter=${IN%%;*}
    echo "> [$iter]"
    # if there's only one element left, set `IN` to an empty string.
    # this causes us to exit this `while` loop.
    # else, we delete the first "element" of the string from IN, and move onto the next.
    [ "$IN" = "$iter" ] && \
        IN='' || \
        IN="${IN#*;}"
  done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

İyi eğlenceler!


15
#, ##, %Ve %%değiştirmelerin (çok onlar silmek nasıl) hatırlamak için daha kolay bir açıklama IMO ne var: #ve %en kısa eşleştirme dizesini silin ve ##ve %%en uzun mümkün silin.
Score_Under

1
Yeni IFS=\; read -a fields <<<"$var"satırlarda başarısız olur ve sondaki satırsonu ekler. Diğer çözüm, boş bir boş alanı kaldırır.
Isaac

Kabuk sınırlayıcı en zarif cevap dönemidir.
Eric Chen

Son alternatif, başka bir yerde ayarlanan alan ayırıcılarının bir listesiyle kullanılabilir mi? Örneğin, bunu bir kabuk komut dosyası olarak kullanmak ve alan ayırıcılarının listesini konumsal parametre olarak iletmek istiyorum.
sancho.s ReinstateMonicaCellio

Evet, bir döngüde:for sep in "#" "ł" "@" ; do ... var="${var#*$sep}" ...
F. Hauri

184

cutKomuta referans veren birkaç cevap gördüm, ama hepsi silindi. Kimsenin bunun üzerinde durmaması biraz garip, çünkü bu tür şeyleri yapmak için, özellikle sınırlandırılmış günlük dosyalarını ayrıştırmak için daha kullanışlı komutlardan biri olduğunu düşünüyorum.

Bu özel örneğin bir bash kod dizisine bölünmesi durumunda tr, muhtemelen daha verimlidir, ancak cutkullanılabilir ve belirli alanları ortasından çekmek istiyorsanız daha etkilidir.

Misal:

$ echo "bla@some.com;john@home.com" | cut -d ";" -f 1
bla@some.com
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 2
john@home.com

Açıkça bir döngüye koyabilir ve her alanı bağımsız olarak çekmek için -f parametresini yineleyebilirsiniz.

Bu, aşağıdaki gibi satırlara sahip sınırlandırılmış bir günlük dosyanız olduğunda daha kullanışlı olur:

2015-04-27|12345|some action|an attribute|meta data

cutcatbu dosyayı açmak ve daha sonraki işlemler için belirli bir alanı seçmek çok kullanışlıdır .


6
Kullanmak için şeref cut, iş için doğru araç! Bu kabuk korsanlarından herhangi birinden çok temizlendi.
MisterMiyagi

4
Bu yaklaşım, yalnızca öğelerin sayısını önceden biliyorsanız çalışır; etrafında biraz daha mantık programlamanız gerekir. Ayrıca her öğe için harici bir araç çalıştırır.
uli42

Excatly waht ben bir csv boş dize önlemek için arıyordu. Şimdi tam 'sütun' değerini de gösterebilirim. Bir döngüde zaten kullanılan IFS ile çalışın. Durumum için beklenenden daha iyi.
Louis Loudog Trottier

Kimlikleri ve PID'leri çekmek için çok kullanışlıdır
Milos Grujic

Bu cevap yarım sayfa aşağı kaydırmaya değer :)
Gucu112

124

Bu benim için çalıştı:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

1
Sadece tek bir karakter sınırlayıcı ile çalışmasına rağmen, OP'nin aradığı şey buydu (noktalı virgülle ayrılmış kayıtlar).
GuyPaddock

@Ashok tarafından yaklaşık dört yıl önce ve ayrıca, bir yıldan fazla bir süre önce @DougW tarafından , daha da fazla bilgi ile cevabınızdan daha fazla cevap verdi. Lütfen diğerlerinden farklı bir çözüm gönderin.
MAChitgarha

90

Bu yaklaşıma ne dersiniz:

IN="bla@some.com;john@home.com" 
set -- "$IN" 
IFS=";"; declare -a Array=($*) 
echo "${Array[@]}" 
echo "${Array[0]}" 
echo "${Array[1]}" 

Kaynak


7
+1 ... ama değişkene "Array" adını vermezdim ... pet peev sanırım. Güzel çözüm.
Yzmir Ramirez

14
+1 ... ancak "set" ve -a bildirimi gereksizdir. Sen de kullanılmış olabilirIFS";" && Array=($IN)
ata

+1 Yalnızca bir not: eski IFS'yi saklayıp geri yüklemeniz önerilmez mi? (stefanB tarafından edit3'te gösterildiği gibi) buraya inen insanlar (bazen sadece bir çözümü kopyalayıp yapıştırarak) bunu düşünmeyebilir
Luca Borrione

6
-1: İlk olarak, @ata bu komutların çoğunun hiçbir şey yapmadığı konusunda haklı. İkincisi, diziyi oluşturmak için sözcük bölmeyi kullanır ve bunu yaparken glob genişlemesini engellemek için hiçbir şey yapmaz (bu nedenle dizi öğelerinden herhangi birinde glob karakterleriniz varsa, bu öğeler eşleşen dosya adlarıyla değiştirilir).
Charles Duffy

1
Kullanım Öner $'...': IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'. Ardından echo "${Array[2]}", yeni satır içeren bir dize yazdıracaktır. set -- "$IN"bu durumda da gereklidir. Evet, glob genişlemesini önlemek için çözüm içermelidir set -f.
John_West

79

AWK , sorununuzu çözmek için en iyi ve verimli komut olduğunu düşünüyorum . AWK, hemen hemen her Linux dağıtımında varsayılan olarak bulunur.

echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'

verecek

bla@some.com john@home.com

Elbette awk yazdırma alanını yeniden tanımlayarak her e-posta adresini saklayabilirsiniz.


3
Ya da daha da basit: echo "bla@some.com; john@home.com" | awk 'BEGIN {RS = ";"} {print}'
Jaro

@Jaro Virgüller içeren bir dizgim olduğunda ve bunları satırlara dönüştürmek gerektiğinde benim için mükemmel çalıştı. Teşekkürler.
Aquarelle

Bu senaryoda çalıştı -> "echo" $ SPLIT_0 "| awk -F 'inode =' '{print $ 1}'"! Karakterler (";") yerine atrings ("inode =") kullanmaya çalışırken sorun yaşadım. 1 $, 2 $, 3 $, 4 $ bir dizide konum olarak ayarlanır! Bir dizi ayarlamak için bir yol varsa ... daha iyi! Teşekkürler!
Eduardo Lucio

@EduardoLucio, yaklaşık belki ne Düşünüyorum öncelikle sınırlayıcı yerini alabilir inode=içine ;örneğin sed -i 's/inode\=/\;/g' your_file_to_process, daha sonra tanımlamak -F';'uyguladığınızda awk, size yardımcı olabilir umut.
Tong

66
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com

4
-1 dize boşluk içeriyorsa ne olur? örneğin IN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ), bu durumda 8 elemandan oluşan bir dizi (ayrılmış her kelime alanı için bir eleman), 2 yerine (ayrılan her satır için iki nokta için bir eleman)
Luca Borrione

3
@Luca Hayır sed komut dosyası tam olarak iki satır oluşturmaz. Sizin için çoklu girişleri yaratan şey, bunu bir bash dizisine yerleştirdiğinizde (varsayılan olarak beyaz boşlukta bölünür)
lothar

Bu tam olarak önemli: OP'nin, düzenlemelerinde görebileceğiniz gibi, girişleri bir dizide saklaması gerekiyor. Sanırım (iyi) cevabınız arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ), bunu başarmak için kullanmaktan IFS=$'\n've gelecekte buraya inecek ve boşluk içeren bir dizeyi bölmek zorunda kalanlar için IFS'yi değiştirmeyi tavsiye etmekten kaçındığını düşünüyorum. (ve daha sonra geri yüklemek için). :)
Luca Borrione

1
@Luca İyi bir nokta. Ancak bu cevabı yazdığımda dizi ataması ilk soruda değildi.
lothar

65

Bu ayrıca işe yarar:

IN="bla@some.com;john@home.com"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

Dikkatli olun, bu çözüm her zaman doğru değildir. Yalnızca "bla@some.com" u geçmeniz durumunda, hem ADD1 hem de ADD2'ye atayacaktır.


1
Belirtilen sorunu önlemek için -s komutunu kullanabilirsiniz: superuser.com/questions/896800/… "-f, --fields = LIST yalnızca bu alanları seçin; -s seçeneği belirtilmedikçe sınırlayıcı karakter içermeyen herhangi bir satırı da yazdırın belirtildi "
fersarr

34

Darron'un cevabına farklı bir bakış , bunu şu şekilde yapıyorum:

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)

Bence öyle! Yukarıdaki komutları çalıştırın ve sonra "echo $ ADDR1 ... $ ADDR2" ve "bla@some.com ... john@home.com" çıktısını
alıyorum

1
Bu benim için gerçekten iyi çalıştı ... Mysqldump kullanmak için virgülle ayrılmış DB, SUNUCU, PORT veri içeren dizeleri dizisi üzerinde yineleme için kullanılır.
Nick

5
Teşhis: IFS=";"atama sadece $(...; echo $IN)alt kabukta bulunur; bu yüzden bazı okuyucular (ben dahil) başlangıçta işe yaramayacağını düşünüyorlar. $ IN tüm ADDR1 tarafından slurped alıyorum varsaydım. Ancak nickjb doğrudur; çalışıyor. Bunun nedeni, echo $INkomutun argümanlarını $ IFS'in geçerli değerini kullanarak ayrıştırmasıdır, ancak daha sonra $ IFS ayarından bağımsız olarak boşluk sınırlayıcı kullanarak stdout'a tekrarlar. Yani net etki, biri çağrılmış gibi read ADDR1 ADDR2 <<< "bla@some.com john@home.com"(girişin boşlukla ayrılmış değil; ayrılmış olduğunu unutmayın).
dubiousjim

1
Bu alanlar ve satırbaşıyla üzerinde başarısız ve aynı zamanda joker genişletmek *de echo $INbir tırnaksız değişken genişleme.
Isaac

Bu çözümü gerçekten çok seviyorum. Neden işe yaradığının bir açıklaması çok yararlı olacaktır ve daha iyi bir genel cevap olacaktır.
Michael Gaskill

32

Bash'te, değişkeniniz yeni satırlar içeriyor olsa bile işe yarayacak kurşun geçirmez bir yol:

IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")

Bak:

$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'

Bunun işe yarayacağı şey -d, read(sınırlayıcı) seçeneğini boş bir sınırlayıcı ile kullanmaktır, böylece readbeslendiği her şeyi okumak zorunda kalır. Ve readdeğişkenin içeriği ile besliyoruz in, çünkü takip eden yeni satır olmadan printf. Ayrıca printf, iletilen dizenin readsonunda bir sınırlayıcı bulunduğundan emin olmak için ayırıcıyı da eklediğimizi unutmayın . Onsuz, readpotansiyel boş alanları keserdi:

$ in='one;two;three;'    # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

sondaki boş alan korunur.


Bash≥4.4 için güncelleme

Bash 4.4'ten beri yerleşik mapfile(aka readarray) -dbir sınırlayıcı belirtme seçeneğini destekler . Dolayısıyla başka bir kanonik yol:

mapfile -d ';' -t array < <(printf '%s;' "$in")

5
Bunu listede \n, boşluklarla ve *eşzamanlı olarak çalışan nadir bir çözüm olarak buldum . Ayrıca, döngü yok; dizi değişkenine, yürütme işleminden sonra kabukta erişilebilir (yükseltilmiş en yüksek yanıtın aksine). Not, in=$'...'çift ​​tırnak işaretleri ile çalışmaz. Bence daha fazla oy gerekiyor.
John_West

28

Dizileri kullanmıyorsanız, bu tek astar hakkında:

IFS=';' read ADDR1 ADDR2 <<<$IN

read -r ...Örneğin, girişteki "\ t" karakterinin değişkenlerinizdeki aynı iki karakterle (tek bir sekme karakteri yerine) bitmesini sağlamak için kullanmayı düşünün .
dubiousjim

-1 Bu burada çalışmıyor (Ubuntu 12.04). Ekleme echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2"Snippet'iniz irade çıkışına ADDR1 bla@some.com john@home.com\nADDR2(\ n yeni satır ise)
Luca Borrione

Bu muhtemelen bir hata içeren IFSve burada bash4.3'te düzeltilen dizeler nedeniyle . Alıntı yapmak $INdüzeltmelidir. (Teoride, $INgenişledikten sonra kelime bölme veya globbinge tabi değildir, bu da tırnak işaretleri gereksiz olmalıdır. 4.3'te bile, en az bir hata kaldı - bildirildi ve düzeltilmesi planlandı - bu nedenle alıntı iyi kalıyor idea.)
chepner 19:15

$ IN kaydedilmiş olsa bile $ in satırında satırsonu varsa bu durum bozulur. Ve sondaki satırsonu ekler.
Isaac

Bununla ilgili bir sorun ve diğer birçok çözüm, $ IN - VEYA'da ADDR2'de ikinci ve sonraki öğelerin birlikte parçalanmasını istediğiniz TAM İKİ eleman olduğunu varsayar. Bunun soruyu karşıladığını anlıyorum, ama bu bir saatli bomba.
Steven the kolayca eğlendirdi

22

IFS'yi ayarlamadan

Sadece bir kolonunuz varsa bunu yapabilirsiniz:

a="foo:bar"
b=${a%:*}
c=${a##*:}

Alacaksın:

b = foo
c = bar

20

İşte temiz bir 3-astar:

in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

burada IFSayırıcıya dayalı sözcükleri sınırlar ve ()bir dizi oluşturmak için kullanılır . Ardından [@]her öğeyi ayrı bir sözcük olarak döndürmek için kullanılır.

Bundan sonra herhangi bir kodunuz varsa $IFS, örneğin geri yüklemeniz de gerekir unset IFS.


5
$inAlıntılanmamış kullanımı joker karakterlerin genişletilmesine izin verir.
Isaac

10

Aşağıdaki Bash / zsh işlevi, ilk bağımsız değişkenini ikinci bağımsız değişken tarafından verilen ayırıcıya böler:

split() {
    local string="$1"
    local delimiter="$2"
    if [ -n "$string" ]; then
        local part
        while read -d "$delimiter" part; do
            echo $part
        done <<< "$string"
        echo $part
    fi
}

Örneğin, komut

$ split 'a;b;c' ';'

verim

a
b
c

Bu çıktı, örneğin, diğer komutlara yönlendirilebilir. Misal:

$ split 'a;b;c' ';' | cat -n
1   a
2   b
3   c

Verilen diğer çözümlerle karşılaştırıldığında, bu aşağıdaki avantajlara sahiptir:

  • IFSgeçersiz kılınmaz: Yerel değişkenlerin dinamik kapsamı nedeniyle, IFSbir döngü üzerinden geçersiz kılma , yeni değerin döngü içinden gerçekleştirilen işlev çağrılarına sızmasına neden olur.

  • Diziler kullanılmaz: Bir dizeyi kullanarak bir diziye okumak için Bash ve zsh'de readbayrak gerekir .-a-A

İstenirse, işlev aşağıdaki gibi bir komut dosyasına yerleştirilebilir:

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"

1 karakterden uzun sınırlayıcılarla çalışmıyor gibi görünüyor: split = $ (split "$ content" "file: //")
madprops

Doğru - itibaren help read:-d delim continue until the first character of DELIM is read, rather than newline
Halle Knast

8

awk'yi birçok duruma uygulayabilirsiniz

echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", $1, $2}'

ayrıca bunu kullanabilirsiniz

echo "bla@some.com;john@home.com"|awk -F';' '{print $1,$2}' OFS="\n"

7

Bunun gibi basit ve akıllı bir yol var:

echo "add:sfff" | xargs -d: -i  echo {}

Ama gnu xargs kullanmalısınız, BSD xargs -d sınırlamasını destekleyemez. Benim gibi apple mac kullanıyorsanız. Gnu xargs yükleyebilirsiniz:

brew install findutils

sonra

echo "add:sfff" | gxargs -d: -i  echo {}

4

Bunu yapmanın en basit yolu bu.

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}

4

Burada bazı güzel cevaplar var (errator esp.), Ancak diğer dillere bölmek için benzer bir şey için - bu, orijinal soruyu aldım demek - buna karar verdim:

IN="bla@some.com;john@home.com"
declare -a a="(${IN/;/ })";

Şimdi ${a[0]}, ${a[1]}vs, beklediğiniz gibi. ${#a[*]}Terim sayısı için kullanın . Ya da yinelemek gerekirse:

for i in ${a[*]}; do echo $i; done

ÖNEMLİ NOT:

Bu, endişelenecek yerlerin olmadığı, sorunumu çözdüğü, ancak sizinkinizi çözemeyeceği durumlarda işe yarar. İle gidin $IFSbu durumda çözelti (ler).


İkiden INfazla e-posta adresi içerdiğinde çalışmaz . Lütfen aynı fikre bakınız (ancak düzeltildi) palindrom'un cevabında
olibre

İkiden ${IN//;/ }fazla değerle çalışmasını sağlamak için daha iyi kullanım (çift eğik çizgi). Herhangi bir joker karakterin ( *?[) genişletileceğini unutmayın. Ve sondaki boş bir alan atılır.
Isaac

3
IN="bla@some.com;john@home.com"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
    echo $entry
done

Çıktı

bla@some.com
john@home.com

Sistem: Ubuntu 12.04.1


IFS readburada belirli bir bağlamda ayarlanmıyor ve bu nedenle varsa kodun geri kalanını bozabilir.
codeforester

2

Yer yoksa, neden olmasın?

IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)

echo ${arr[0]}
echo ${arr[1]}

2

Diziyi setyüklemek için yerleşik olanı kullanın $@:

IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'

Sonra partinin başlamasına izin verin:

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2

set -- $INDash ile başlayan "$ IN" ile ilgili bazı sorunları önlemek için daha iyi kullanın . Yine de, $INdeğerinin genişletilmemiş genişletmesi joker karakterleri ( *?[) genişletecektir .
Isaac

2

Her ikisinin bash dizileri gerektirmediği iki bourne-ish alternatifi:

Durum 1 : Güzel ve basit tutun: Kayıt Ayırıcı olarak NewLine kullanın ... örn.

IN="bla@some.com
john@home.com"

while read i; do
  # process "$i" ... eg.
    echo "[email:$i]"
done <<< "$IN"

Not: Bu ilk durumda, liste manipülasyonuna yardımcı olacak hiçbir alt süreç çatallanmaz.

Fikir: Belki de NL'yi dahili olarak kapsamlı bir şekilde kullanmaya ve harici olarak nihai sonucu oluştururken sadece farklı bir RS'ye dönüştürmeye değer .

Durum 2 : Bir ";" bir kayıt ayırıcı olarak ... örn.

NL="
" IRS=";" ORS=";"

conv_IRS() {
  exec tr "$1" "$NL"
}

conv_ORS() {
  exec tr "$NL" "$1"
}

IN="bla@some.com;john@home.com"
IN="$(conv_IRS ";" <<< "$IN")"

while read i; do
  # process "$i" ... eg.
    echo -n "[email:$i]$ORS"
done <<< "$IN"

Her iki durumda da, döngü tamamlandıktan sonra döngü içinde bir alt liste oluşturulabilir. Bu, listeleri listelerde değiştirirken, dosyaları listelerde saklarken yararlıdır. {ps sakin olun ve devam edin B-)}


2

Daha önce verilen fantastik cevapların yanı sıra, sadece kullanmayı düşündüğünüz verileri yazdırmakla ilgiliyse awk:

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

Bu, alan ayırıcısını ;, bir döngü ile alanlar arasında fordöngü yapıp buna göre yazdıracak şekilde ayarlar .

Ölçek

$ IN="bla@some.com;john@home.com"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [bla@some.com]
> [john@home.com]

Başka bir girişle:

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]

2

Android kabuğunda, önerilen yöntemlerin çoğu işe yaramaz:

$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

Ne işe yarar:

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin

burada //küresel değiştirme anlamına gelir.


1
$ PATH öğesinin herhangi bir parçası boşluk (veya yeni satır) içeriyorsa başarısız olur. Ayrıca joker karakterleri (yıldız işareti *, soru işareti? Ve parantez […]) genişletir.
Isaac

2
IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

Çıktı:

bla@some.com
john@home.com
Charlie Brown <cbrown@acme.com
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

Açıklama: Parantez () kullanılarak yapılan basit atama, noktalı virgülle ayrılmış listeyi, bunu yaparken doğru IFS'ye sahip olmanız koşuluyla bir diziye dönüştürür. Standart FOR döngüsü, bu dizideki öğeleri tek tek her zamanki gibi işler. IN değişkeni için verilen listenin "sert", yani tek kenelerle belirtilmesi gerektiğine dikkat edin.

Bash bir atamaya komutla aynı şekilde davranmadığı için IFS kaydedilmeli ve geri yüklenmelidir. Alternatif bir çözüm, atamayı bir işlevin içine sarmak ve bu işlevi değiştirilmiş bir IFS ile çağırmaktır. Bu durumda, IFS'nin ayrı olarak kaydedilmesi / geri yüklenmesi gerekmez. "Bize" işaret ettiğiniz için teşekkür ederiz.


!"#$%&/()[]{}*? are no problemiyi ... tam olarak değil: []*?glob karakterler. Peki bu dizini ve dosyayı oluşturmaya ne dersiniz: `mkdir '!" # $% &'; '' Düğmesine dokunun! basit güzel olabilir, ama kırıldığında kırılır.
gniourf_gniourf

@gniourf_gniourf Dize bir değişkende saklanır. Lütfen orijinal soruya bakın.
ajaaskel

1
@ajaaskel yorumumu tam olarak anlamadın. Bir çizik dizine gidin ve bu komutlar vermek: mkdir '!"#$%&'; touch '!"#$%&/()[]{} got you hahahaha - are no problem'. Sadece bir dizin ve bir dosya oluşturacaklar, garip görünümlü isimlerle itiraf etmeliyim. Sonra tam ile komutları çalıştırın INsize vermiştir: IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'. Beklediğiniz çıktıyı alamayacağınızı göreceksiniz. Çünkü dizenizi bölmek için yol adı genişletmelerine tabi bir yöntem kullanıyorsunuz.
gniourf_gniourf

Bu karakter olduğunu göstermektir *, ?, [...]ancak ve hatta, extglobayarlanır, !(...), @(...), ?(...), +(...) olan bu yöntemle sorunlar!
gniourf_gniourf

1
@gniourf_gniourf Globbing hakkındaki detaylı yorumlar için teşekkürler. Ben kod kapalı globbing ayarlayın. Ancak benim açımdan, basit bir görevin bölünme işini yapabileceğini göstermekti.
ajaaskel

1

Tamam çocuklar!

İşte cevabım!

DELIMITER_VAL='='

read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF

SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
   SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
    echo "$i"
done

Neden bu yaklaşım benim için "en iyisi"?

İki nedenden dolayı:

  1. Sınırlayıcıdan kaçmanıza gerek yoktur ;
  2. Boş alanlarda sorun yaşamayacaksınız . Değer dizide düzgün şekilde ayrılacaktır!

[] 'İn


Bilginize, /etc/os-releaseve /etc/lsb-releasekaynaklı ve ayrıştırılmamış olmak içindir. Yani yönteminiz gerçekten yanlış. Dahası, bir dizgiyi sınırlayıcıya dökmekle
gniourf_gniourf

0

';' İle ayrılmış bir dizeyi bölmek için tek astar bir diziye:

IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

Bu yalnızca IFS'yi bir alt kabukta ayarlar, bu nedenle değerini kaydetme ve geri yükleme konusunda endişelenmenize gerek yoktur.


-1 bu işe yaramıyor (ubuntu 12.04). yalnızca $ IN değerinin olduğu ilk yankıyı yazdırırken, ikincisi boş. echo "0:" $ {ADDRS [0]} \ n echo "1:" $ {ADDRS [1]} koyarsanız çıktıyı görebilirsiniz 0: bla@some.com;john@home.com\n 1:(\ n yeni satırdır)
Luca Borrione

1
bu fikre çalışan bir alternatif için lütfen nickjb'nin cevabına bakın stackoverflow.com/a/6583589/1032370
Luca Borrione

1
-1, 1. IFS bu alt kabukta ayarlanmadı (bir yerleşik olan "echo" ortamına aktarılıyor, bu yüzden zaten hiçbir şey olmuyor). 2. $INIFS bölünmesine maruz kalmayacak şekilde alıntılanmıştır. 3. İşlem ikamesi boşlukla bölünür, ancak bu orijinal verileri bozabilir.
Puan_28

0

Belki de en zarif çözüm değil, aynı zamanda *ve boşluklarla çalışır :

IN="bla@so me.com;*;john@home.com"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

çıktılar

> [bla@so me.com]
> [*]
> [john@home.com]

Diğer örnek (başlangıç ​​ve bitişteki sınırlayıcılar):

IN=";bla@so me.com;*;john@home.com;"
> []
> [bla@so me.com]
> [*]
> [john@home.com]
> []

Temelde, örneğin ;yapmaktan başka her karakteri kaldırır delims. ;;;. Sonra yapar fordöngüyü 1için number-of-delimitersile sayılmıştır olarak ${#delims}. Son adım, bu $iparçayı kullanarak güvenli bir şekilde elde etmektir cut.

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.