IFS (Dahili Alan Ayırıcı), birbirini izleyen birden fazla sınırlayıcı karakter için tek bir ayırıcı işlevi görebilir mi?


10

Bir diziyi beyaz olmayan boşluk değerleriyle IFS kullanarak ayrıştırmak boş öğeler oluşturur. Tek bir sınırlama için birden fazla sınırlamayı daraltmak
bile tr -syeterli değildir.
Bir örnek sorunu daha açık
bir şekilde açıklayabilir .. Bir IFS ince ayarı ile "normal" sonuçlar elde etmenin bir yolu var mı (IFS'in davranışını değiştirmek için ilişkili bir ayar var mı? .... yani. Varsayılan boşlukla aynı şekilde hareket etmek IFS.

var=" abc  def   ghi    "
echo "============== IFS=<default>"
arr=($var)
for x in ${!arr[*]} ; do
   echo "# arr[$x] \"${arr[x]}\""
done
#
sfi="$IFS" ; IFS=':'
set -f # Disable file name generation (globbing)
       # (This  data won't "glob", but unless globbing     
       #  is actually needed, turn if off, because   
       #  unusual/unexpected combinations of data can glob!
       #  and they can do it in the most obscure ways...  
       #  With IFS, "you're not in Kansas any more! :)  
var=":abc::def:::ghi::::"
echo "============== IFS=$IFS"
arr=($var)
for x in ${!arr[*]} ; do
   echo "# arr[$x] \"${arr[x]}\""
done
echo "============== IFS=$IFS and tr"
arr=($(echo -n "$var"|tr -s "$IFS"))
for x in ${!arr[*]} ; do
   echo "# arr[$x] \"${arr[x]}\""
done
set +f     # enable globbing 
IFS="$sfi" # re-instate original IFS val
echo "============== IFS=<default>"

İşte çıktı


============== IFS=<default>
# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"
============== IFS=:
# arr[0] ""
# arr[1] "abc"
# arr[2] ""
# arr[3] "def"
# arr[4] ""
# arr[5] ""
# arr[6] "ghi"
# arr[7] ""
# arr[8] ""
# arr[9] ""
============== IFS=: and tr
# arr[0] ""
# arr[1] "abc"
# arr[2] "def"
# arr[3] "ghi"
============== IFS=<default>

Aynı soruya daha iyi (bence) bir cevap var : stackoverflow.com/a/14789518/1765658
F. Hauri

Yanıtlar:


3

Birden fazla (boşluk olmayan) ardışık sınırlayıcı karakteri kaldırmak için iki (dize / dizi) parametre genişletmesi kullanılabilir. Hile, IFSdeğişkeni dizi parametresi genişletmesi için boş dizeye ayarlamaktır .

Bu, Word Bölmeman bash altında belgelenmiştir :

Hiçbir değeri olmayan parametrelerin genişletilmesinden kaynaklanan alıntılanmamış örtük null argümanlar kaldırılır.

(
set -f
str=':abc::def:::ghi::::'
IFS=':'
arr=(${str})
IFS=""
arr=(${arr[@]})

echo ${!arr[*]}

for ((i=0; i < ${#arr[@]}; i++)); do 
   echo "${i}: '${arr[${i}]}'"
done
)

İyi! Basit ve etkili bir yöntem - bash döngüsüne gerek kalmadan ve bir yardımcı program çağırmaya gerek kalmadan - BTW. "(Boşluk olmayan)" dan bahsettiğiniz gibi , netlik için, boşluk dahil herhangi bir sınırlayıcı karakter kombinasyonu ile iyi çalıştığına dikkat çekerim.
Peter.O

Testlerimde ayar IFS=' '(yani bir boşluk) aynı şekilde davranır. Bunu açık bir null argümanından ("" veya '') daha az kafa karıştırıcı buluyorum IFS.
Micha Wiedenmann

Verileriniz gömülü boşluk içeriyorsa, bu korkunç bir çözümdür. Bu, verileriniz 'abc' yerine 'bc' olsaydı, IFS = "" 'a' yı "bc" den ayrı bir öğeye bölerdi.
Dejay Clayton

5

Gönderen bashman:

IFS'de IFS boşluk olmayan herhangi bir karakter, bitişik IFS boşluk karakterleriyle birlikte bir alanı sınırlar. Bir dizi IFS boşluk karakteri de sınırlayıcı olarak ele alınır.

IFS boşluk (boşluk, sekme ve satırsonu) diğer ayırıcılar gibi işlem görmediği anlamına gelir . Alternatif bir ayırıcı ile tam olarak aynı davranışı elde etmek istiyorsanız, trveya yardımıyla aşağıdaki ayırıcıları değiştirebilirsiniz sed:

var=":abc::def:::ghi::::"
arr=($(echo -n $var | sed 's/ /%#%#%#%#%/g;s/:/ /g'))
for x in ${!arr[*]} ; do
   el=$(echo -n $arr | sed 's/%#%#%#%#%/ /g')
   echo "# arr[$x] \"$el\""
done

%#%#%#%#%Şey alanları içindeki olası boşlukların yerine bir sihirli değerdir, "eşsiz" (ya da çok unlinkely) olması bekleniyor. Alanlarda hiç boşluk kalmayacağından eminseniz, bu bölümü bırakın).


@FussyS ... Teşekkürler (sorumdaki modificatona bakınız) ... Bana amaçladığım soruya cevap vermiş olabilirsiniz .. ve bu cevap olabilir (muhtemelen) "IFS'nin istediğim şekilde "... ben trsorunu göstermek için örnekleri intendet ... Bir sistem çağrısından kaçınmak istiyorum, bu yüzden ${var##:}benim yorumda glen's ansewer bahsettiğim ötesinde bir bash seçeneği bakacağız .... Bir süre bekleyeceğim .. belki IFS'yi koaksiyel hale getirmenin bir yolu var, aksi takdirde cevabınızın ilk kısmı
peşindeydi

Bu muamele IFStüm Bourne tarzı mermilerde aynıdır, POSIX'te belirtilmiştir .
Gilles 'SO- kötü olmayı kes'

4-plus yıllar bu soruyu sordum beri - ben nazad'ın cevabını buldum (bir yıl önce yayınlanmıştır) IFSsınırlayıcı-dize olarak herhangi bir sayı ve karakter kombinasyonu ile bir dizi oluşturmak için IFS hokkabazlık en basit yolu olarak bulundu . Benim sorum en iyi yanıtı verdi jon_d, ancak @ nazad'ın cevabı IFSdöngüsüz ve yardımcı uygulama olmadan kullanmanın şık bir yolunu gösteriyor .
Peter.O

2

Bash IFS, ardışık sınırlayıcı karakterleri tek bir sınırlayıcı olarak (boşluk olmayan sınırlayıcılar için) tedavi etmek için dahili bir yol sağlamadığından, tüm bir bash sürümünü bir araya getirdim (örneğin, harici bir çağrı kullanmak gibi. Tr, awk, sed) )

Çoklu karakter IFS işleyebilir ..

İşte bu Q / A sayfasında gösterilen seçenekler trve awkseçenekler için benzer testlerle birlikte yürütme zamanı özgeçmişleri ... Testler, sadece diziyi (G / Ç olmadan) oluşturmanın 10000 yinelemesine dayanıyor ...

pure bash     3.174s (28 char IFS)
call (awk) 0m32.210s  (1 char IFS) 
call (tr)  0m32.178s  (1 char IFS) 

İşte çıktı

# dlm_str  = :.~!@#$%^&()_+-=`}{][ ";></,
# original = :abc:.. def:.~!@#$%^&()_+-=`}{][ ";></,'single*quote?'..123:
# unified  = :abc::::def::::::::::::::::::::::::::::'single*quote?'::123:
# max-w 2^ = ::::::::::::::::
# shrunk.. = :abc:def:'single*quote?':123:
# arr[0] "abc"
# arr[1] "def"
# arr[2] "'single*quote?'"
# arr[3] "123"

İşte senaryo

#!/bin/bash

# Note: This script modifies the source string. 
#       so work with a copy, if you need the original. 
# also: Use the name varG (Global) it's required by 'shrink_repeat_chars'
#
# NOTE: * asterisk      in IFS causes a regex(?) issue,     but  *  is ok in data. 
# NOTE: ? Question-mark in IFS causes a regex(?) issue,     but  ?  is ok in data. 
# NOTE: 0..9 digits     in IFS causes empty/wacky elements, but they're ok in data.
# NOTE: ' single quote  in IFS; don't know yet,             but  '  is ok in data.
# 
function shrink_repeat_chars () # A 'tr -s' analog
{
  # Shrink repeating occurrences of char
  #
  # $1: A string of delimiters which when consecutively repeated and are       
  #     considered as a shrinkable group. A example is: "   " whitespace delimiter.
  #
  # $varG  A global var which contains the string to be "shrunk".
  #
# echo "# dlm_str  = $1" 
# echo "# original = $varG" 
  dlms="$1"        # arg delimiter string
  dlm1=${dlms:0:1} # 1st delimiter char  
  dlmw=$dlm1       # work delimiter  
  # More than one delimiter char
  # ============================
  # When a delimiter contains more than one char.. ie (different byte` values),    
  # make all delimiter-chars in string $varG the same as the 1st delimiter char.
  ix=1;xx=${#dlms}; 
  while ((ix<xx)) ; do # Where more than one delim char, make all the same in varG  
    varG="${varG//${dlms:$ix:1}/$dlm1}"
    ix=$((ix+1))
  done
# echo "# unified  = $varG" 
  #
  # Binary shrink
  # =============
  # Find the longest required "power of 2' group needed for a binary shrink
  while [[ "$varG" =~ .*$dlmw$dlmw.* ]] ; do dlmw=$dlmw$dlmw; done # double its length
# echo "# max-w 2^ = $dlmw"
  #
  # Shrik groups of delims to a single char
  while [[ ! "$dlmw" == "$dlm1" ]] ; do
    varG=${varG//${dlmw}$dlm1/$dlm1}
    dlmw=${dlmw:$((${#dlmw}/2))}
  done
  varG=${varG//${dlmw}$dlm1/$dlm1}
# echo "# shrunk.. = $varG"
}

# Main
  varG=':abc:.. def:.~!@#$%^&()_+-=`}{][ ";></,'\''single*quote?'\''..123:' 
  sfi="$IFS"; IFS=':.~!@#$%^&()_+-=`}{][ ";></,' # save original IFS and set new multi-char IFS
  set -f                                         # disable globbing
  shrink_repeat_chars "$IFS" # The source string name must be $varG
  arr=(${varG:1})    # Strip leading dlim;  A single trailing dlim is ok (strangely
  for ix in ${!arr[*]} ; do  # Dump the array
     echo "# arr[$ix] \"${arr[ix]}\""
  done
  set +f     # re-enable globbing   
  IFS="$sfi" # re-instate the original IFS
  #
exit

Harika iş, ilginç +1!
F. Hauri

1

Bunu gawk ile de yapabilirsiniz, ancak hoş değil:

var=":abc::def:::ghi::::"
out=$( gawk -F ':+' '
  {
    # strip delimiters from the ends of the line
    sub("^"FS,"")
    sub(FS"$","")
    # then output in a bash-friendly format
    for (i=1;i<=NF;i++) printf("\"%s\" ", $i)
    print ""
  }
' <<< "$var" )
eval arr=($out)
for x in ${!arr[*]} ; do
  echo "# arr[$x] \"${arr[x]}\""
done

çıktılar

# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"

... Bu sadece benim değiştirerek bunu yapmak için yeterince kolay sayesinde ... Ben benim ana isteği (modifiye soru) net olmamıştır var gibi var $varetmek ${var##:}... Gerçekten bir yol sonra IFS kendini çimdik edildi .. İstediğim harici bir çağrı olmadan bunu yapmak için (bash bunu herhangi bir harici kutudan daha etkili bir şekilde yapabileceğini hissediyorum ... bu yüzden bu yolda devam edeceğim) ... yönteminiz çalışıyor (+1) .... Şimdiye kadar giriş değiştirmek gibi, ben awk veya tr yerine bash ile denemek tercih ederim (bir sistem çağrısı önlemek olurdu), ama gerçekten bir IFS tweak için takılmak ...
Peter.O

@fred, belirtildiği gibi, IFS yalnızca varsayılan boşluk değeri için art arda birden fazla sınırlayıcı yukarı kaydırır. Aksi takdirde, ardışık sınırlayıcılar, boş boş alanlara neden olur. Bir veya iki harici çağrının performansı herhangi bir şekilde etkilemesi son derece düşüktür.
glenn jackman

@glen .. (Cevabınızın "güzel" olmadığını söylediniz .. Sanırım öyle! :) Ancak, tüm bash versiyonunu (harici çağrıya karşı) bir araya getirdim ve sadece arrray'ı ( G / Ç yok) ... bash 1.276s... call (awk) 0m32.210s,,, call (tr) 0m32.178s... Bunu birkaç kez yapın ve bash'ın yavaş olduğunu düşünebilirsiniz! ... Bu durumda awk daha kolay mı? ... eğer pasajı zaten aldıysanız değil :) ... daha sonra göndereceğim; şuan gitmek zorunda.
Peter.O

Bu arada, gawk betiğiniz yeniden ... Daha önce awk kullanmadım, bu yüzden (ve diğerlerini) ayrıntılı olarak inceledim ... Nedenini seçemiyorum, ama bahsedeceğim her nasılsa .. Alıntı verilen veriler verildiğinde, tırnakları kaybeder ve tırnak arasındaki boşluklara böler .. ve tek sayıda tırnak için çöküyor ... İşte test verileri:var="The \"X\" factor:::A single '\"' crashes:::\"One Two\""
Peter.O

-1

Basit cevap şudur: tüm sınırlayıcıları birine (ilkine) daraltın.
Bu bir döngü gerektirir (bu da log(N)zamandan az çalışır )

 var=':a bc::d ef:#$%_+$$%      ^%&*(*&*^
 $#,.::ghi::*::'                           # a long test string.
 d=':@!#$%^&*()_+,.'                       # delimiter set
 f=${d:0:1}                                # first delimiter
 v=${var//["$d"]/"$f"};                    # convert all delimiters to
 :                                         # the first of the delimiter set.
 tmp=$v                                    # temporal variable (v).
 while
     tmp=${tmp//["$f"]["$f"]/"$f"};        # collapse each two delimiters to one
     [[ "$tmp" != "$v" ]];                 # If there was a change
 do
     v=$tmp;                               # actualize the value of the string.
 done

Tek yapmanız gereken dizeyi bir sınırlayıcıya doğru şekilde bölmek ve yazdırmaktır:

 readarray -td "$f" arr < <(printf '%s%s' "$v"'' "$f")
 printf '<%s>' "${arr[@]}" ; echo

set -fIFS değiştirmeye veya değiştirmeye gerek yok .
Boşluklar, yeni satırlar ve glob karakterlerle test edildi. Bütün iş. Oldukça yavaş (bir kabuk döngüsünün olması gerektiği gibi).
Ama sadece bash için ( -dyeniden basma seçeneği nedeniyle bash 4.4+ ).


sh

Kabuk sürümü bir dizi kullanamaz, kullanılabilir tek dizi konum parametreleridir.
Kullanmak tr -ssadece bir satırdır (IFS kodda değişmez):

 set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'

Ve yazdırın:

 printf '<%s>' "$@" ; echo

Hala yavaş, ama çok fazla değil.

commandBourne'de komut geçersiz.
Zsh'de, commandyalnızca harici komutları çağırır ve commandkullanılırsa eval başarısız olur .
Ksh commandcinsinden bile , IFS değeri küresel kapsamda değiştirilir.
Ve commandmksh ile ilgili mermilerde (mksh, lksh, posh) ayrılmayı başarısız kılıyor Komutun kaldırılması command, kodun daha fazla mermi üzerinde çalışmasını sağlar. Ancak: kaldırma command, IFS'nin bash (posix modu olmadan) ve zsh varsayılan (öykünme yok) modu hariç çoğu kabukta (eval özel bir yerleşiktir) değerini koruyacaktır. Bu kavram varsayılan zsh ile ya da onsuz çalışmak için yapılamaz command.


Çok karakterli IFS

Evet, IFS çok karakterli olabilir, ancak her karakter bir bağımsız değişken oluşturur:

 set -f; IFS="$d" command eval set -- '$(echo "$var" )""'
 printf '<%s>' "$@" ; echo

Çıktı olacak:

 <><a bc><><d ef><><><><><><><><><      ><><><><><><><><><
 ><><><><><><ghi><><><><><>

Bash ile commandsh / POSIX emülasyonunda değilse kelimeyi atlayabilirsiniz . Komut ksh93'te başarısız olur (IFS değiştirilen değeri tutar). Zsh'de komut command, zsh'ı evalharici bir komut olarak bulmaya çalışır (bulamaz) ve başarısız olur.

Olan şey, bir sınırlayıcıya otomatik olarak daraltılmış olan yalnızca IFS karakterlerinin IFS beyaz alanı olmasıdır.
IFS'deki bir boşluk, ardışık tüm boşlukları bire daraltır. Bir sekme tüm sekmeleri daraltır. Bir boşluk ve bir sekme boşlukları ve / veya sekmeleri bir sınırlayıcıya daraltır. Fikri newline ile tekrarlayın.

Birkaç sınırlayıcıyı daraltmak için biraz hokkabazlık yapmak gerekir.
Girişte ASCII 3 (0x03) kullanılmadığı varsayılarak var:

 var=${var// /$'\3'}                       # protect spaces
 var=${var//["$d"]/ }                      # convert all delimiters to spaces
 set -f;                                   # avoid expanding globs.
 IFS=" " command eval set -- '""$var""'    # split on spaces.
 set -- "${@//$'\3'/ }"                    # convert spaces back.

Ksh, zsh ve bash (about commandve IFS) hakkındaki yorumların çoğu burada hala geçerlidir.

$'\0'Metin girişinde değeri daha düşük olabilir, ancak bash değişkenleri NUL ( 0x00) içeremez .

Sh'de aynı dize işlemlerini yapmak için dahili komutlar yoktur, bu nedenle tr, sh komut dosyaları için tek çözümdür.


Evet, OP'nin istediği kabuk için şunu yazdım: Bash. Bu kabukta IFS tutulmaz. Ve evet, örneğin zsh için taşınabilir değildir. StéphaneChazelas
Isaac

Bash ve zsh durumunda, sh olarak çağrıldığında
POSIX'in

@ StéphaneChazelas Her kabuğun sınırlamaları hakkında (birçok) not eklendi.
Isaac

@ StéphaneChazelas Neden inişli çıkışlı?
Isaac

Bilmiyorum, ben değildim. BTW, burada command evalIIRC hakkında Gilles tarafından özel bir Soru-Cevap olduğunu düşünüyorum
Stéphane Chazelas
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.