Tek bir ifade için IFS ayarı


42

Özel bir IFS değerinin tek bir komut / yerleşik kapsam için ayarlanabileceğini biliyorum. Tek bir ifade için özel bir IFS değeri belirlemenin bir yolu var mı? Görünüşe göre, aşağıdakilere dayanarak, küresel IFS değeri bu denendiğinde etkilendiğinden

#check environment IFS value, it is space-tab-newline
printf "%s" "$IFS" | od -bc
0000000 040 011 012
             \t  \n
0000003
#invoke built-in with custom IFS
IFS=$'\n' read -r -d '' -a arr <<< "$str"
#environment IFS value remains unchanged as seen below
printf "%s" "$IFS" | od -bc
0000000 040 011 012
             \t  \n
0000003

#now attempt to set IFS for a single statement
IFS=$'\n' a=($str)
#BUT environment IFS value is overwritten as seen below
printf "%s" "$IFS" | od -bc
0000000 012
         \n
     0000001

Yanıtlar:


39

Bazı mermilerde (dahil bash):

IFS=: command eval 'p=($PATH)'

(ile bash, commandsh / POSIX emülasyonunda değilse ihmal edebilirsiniz ). Ancak, alıntılanmamış değişkenleri kullanırken, genel olarak buna ihtiyaç duyduğunuzdan set -fve çoğu kabukta bunun için yerel bir kapsamın olmadığına dikkat edin.

Zsh ile şunları yapabilirsiniz:

(){ local IFS=:; p=($=PATH); }

$=PATHvarsayılan olarak zshgirilmeyen kelimeyi bölmeyi zorlamaktır (değişken genişlemenin üzerinde işlem yapmak ya da set -femülasyonda olmadıkça).

(){...}(veya function {...}) isimsiz işlevler olarak adlandırılır ve genellikle yerel bir kapsam belirlemek için kullanılır. işlevlerde yerel kapsamı destekleyen diğer kabuklarla, şunlara benzer bir şey yapabilirsiniz:

e() { eval "$@"; }
e 'local IFS=:; p=($PATH)'

POSIX mermilerinde değişkenler ve seçenekler için yerel kapsam uygulamak için, https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh adresinde verilen işlevleri de kullanabilirsiniz . O zaman olarak kullanabilirsiniz:

. /path/to/locvar.sh
var=3,2,2
call eval 'locvar IFS; locopt -f; IFS=,; set -- $var; a=$1 b=$2 c=$3'

(bu arada, diğer mermilerdeki gibi, $PATHyukarıdaki şekilde bölüştürmek geçersizdir zsh, IFS alan ayırıcıdır, alan ayırıcı değildir).

IFS=$'\n' a=($str)

Sadece iki ödev, birbiri ardına olduğu gibi a=1 b=2.

İle ilgili açıklama notu var=value cmd:

İçinde:

var=value cmd arg

Kabuk yürütür /path/to/cmdyeni bir işlem ve geçiş bölgesi cmdve argde argv[]ve var=valuede envp[]. Bu gerçekten değişken bir atama değil, exec komutuna geçen ortam değişkenlerinin sayısıdır . Bourne veya Korn kabuğunda, set -khatta yazabilirsiniz cmd var=value arg.

Şimdi bu, yürütülmeyen yerleşiklere veya işlevlere uygulanmaz . Bourne kabuğu, içinde var=value some-builtin, varyukarı uçları sadece olduğu gibi, sonradan ayarlanan var=valueyalnız. Bu, örneğin var=value echo foo(yararlı olmayan) davranışının echoyerleşik olup olmamasına bağlı olarak değiştiği anlamına gelir .

POSIX ve / veya kshBourne davranışının yalnızca özel yerleşikler adı verilen bir kategori türü için gerçekleştiğini değiştirmiştir . evalözel bir yapı, readöyle değil. Özel olmayan yerleşikler için, yalnızca harici bir komut çalıştırıldığında benzer şekilde davranmasını sağlayan yerleşikin yürütülmesi için var=value builtinayarlar var.

commandKomut kaldırmak için kullanılabilecek özel olanların niteliğini özel yerleşikleri . POSIX'in göz ardı ettiği şey şu ki, evalve .builtins için, kabukların değişken bir yığın uygulamak zorunda kalacağı anlamına geliyordu (bunun için localya da typesetkapsam sınırlama komutlarını belirtmese bile ):

a=0; a=1 command eval 'a=2 command eval echo \$a; echo $a'; echo $a

Ya da:

a=1 command eval myfunction

ile myfunctionbir fonksiyonu olduğu kullanarak veya ayar $ave potansiyel çağırarak command eval.

Bu gerçekten göz ardı edildi, çünkü ksh(en çok dayanan spesifikasyondu) bunu uygulamamıştı (ve AT&T kshve zshhala yok), ama bugünlerde, bu iki hariç, çoğu mermi onu uyguluyor. Davranış kabukları arasında farklılık gösterir:

a=0; a=1 command eval a=2; echo "$a"

rağmen. localYerel kapsamı uygulamak için onu destekleyen mermileri kullanmak daha güvenilir bir yoldur.


Tuhaftır, IFS=: command eval …setleri IFSsadece süresince evalKsh 93u içinde tire, pdksh ve bash, POSIX tarafından zorunlu değil gibi. Ksh’ın garip olmayan bir-bir-out olduğunu görmek alışılmadık bir durum.
Gilles 'SO- kötülük' dur

12

Kernighan ve Pike tarafından "Unix Programlama Ortamı" ndan alınan standart kaydetme ve geri yükleme:

#!/bin/sh
old_IFS=$IFS
IFS="something_new"
some_program_or_builtin
IFS=${old_IFS}

2
teşekkür ederim ve +1. Evet bu seçeneğin farkındayım, ancak ne demek istediğimi biliyorsanız "temizleyici" seçeneğinin olup olmadığını bilmek istiyorum
iruvar

Yarı-kolonlu bir çizgide sıkıştırabilirsin, ama bunun daha temiz olduğunu sanmıyorum. ); Her şey vardı özel sözdizimsel desteği dile istiyorsa daha güzel olabilir, ama o zaman muhtemelen kodlama yerine sumptin marangozluk veya öğrenmek olurdu
msw

9
Daha $IFSönce ayarlanmamışsa, doğru şekilde geri yüklenemez.
Stéphane Chazelas 24:13

2
Bu ayar kaldırılamaz o kadar Bash davranır ise $'\t\n'' ': burada açıkladı wiki.bash-hackers.org/syntax/expansion/...
davide

2
@ ver, bu olurdu $' \t\n'. Uzay, bunun için kullanılan ilk olmak zorunda "$*". Tüm Bourne benzeri mermilerde aynı olduğuna dikkat edin.
Stéphane Chazelas

8

Komut dosyanızı bir işleve koyun ve bu işlevi komut satırı argümanlarını ileterek çağırın. IFS yerel olarak tanımlandığı için, değişiklikler küresel IFS'yi etkilemez.

main() {
  local IFS='/'

  # the rest goes here
}

main "$@"

6

Bu komut için:

IFS=$'\n' a=($str)

Alternatif bir çözüm var: ilk atama ( IFS=$'\n') yürütmek için bir komut (bir işlev) vermek için:

$ split(){ a=( $str ); }
$ IFS=$'\n' split

Bu, IFS'yi çevreye bölünme çağrısı yapacak ancak mevcut ortamda tutmayacaktır.

Bu ayrıca eval'ün her zaman riskli kullanımını engeller.


Ksh93 ve mksh'de ve POSIX modundayken bash ve zsh, POSIX tarafından istenildiği şekilde $IFSayarlanmış halde kalır $'\n'.
Stéphane Chazelas

4

@ Helpermethod tarafından önerilen cevap kesinlikle ilginç bir yaklaşım. Ancak bu aynı zamanda bir tuzaktır, çünkü BASH'de yerel değişken kapsamı arayandan çağrılan işleve uzanır. Bu nedenle, IFS'yi main () olarak ayarlamak, bu değerin main () denilen işlevlere kalıcı olmasını sağlar. İşte bir örnek:

#!/usr/bin/env bash
#
func() {
  # local IFS='\'

  local args=${@}
  echo -n "$FUNCNAME A"
  for ((i=0; i<${#args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${args[$i]}"
  done
  echo

  local f_args=( $(echo "${args[0]}") )
  echo -n "$FUNCNAME B"
  for ((i=0; i<${#f_args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${f_args[$i]}  "
  done
  echo
}

main() {
  local IFS='/'

  # the rest goes here
  local args=${@}
  echo -n "$FUNCNAME A"
  for ((i=0; i<${#args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${args[$i]}"
  done
  echo

  local m_args=( $(echo "${args[0]}") )
  echo -n "$FUNCNAME B"
  for ((i=0; i<${#m_args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${m_args[$i]}  "
  done
  echo

  func "${m_args[*]}"
}

main "$@"

Ve çıktı ...

main A[0]: ick/blick/flick
main B[0]: ick  [1]: blick  [2]: flick
func A[0]: ick/blick/flick
func B[0]: ick  [1]: blick  [2]: flick

Eğer main () 'de ilan edilen IFS func ()' de hala kapsamda değilse, o zaman dizi func () B 'de düzgün bir şekilde ayrıştırılmazdı.

main A[0]: ick/blick/flick
main B[0]: ick  [1]: blick  [2]: flick
func A[0]: ick/blick/flick
func B[0]: ick/blick/flick

Eğer IFS kapsam dışına çıksaydı, almanız gereken şey buydu.

Çok daha iyi bir çözüm IMHO, küresel / yerel düzeyde IFS'in değişmesini veya buna dayanmasını öngörmektir. Bunun yerine, yeni bir kabuk oluşturun ve orada IFS ile oynayın. Örneğin, main () işlevinde func () işlevini aşağıdaki gibi çağırırsanız, diziyi geriye doğru eğik çizgi alan ayırıcılı bir dize olarak geçirmek:

func $(IFS='\'; echo "${m_args[*]}")

... IFS’deki bu değişiklik func () işlevine yansıtılmayacak. Dizi bir dizge olarak geçirilecektir:

ick\blick\flick

... ancak func () işlevinde IFS, func () konumunda yerel olarak değiştirilmediği sürece, hala "/" (main () 'de belirtildiği gibi) olacaktır.

IFS’de yapılan değişikliklerin yalıtılması hakkında daha fazla bilgi aşağıdaki bağlantılarda görülebilir:

Bir bash dizi değişkenini newlines ile ayrılmış bir dizeye nasıl dönüştürebilirim?

IFS ile dizilen Bash dizgisi

Genel kabuk betiği programlaması için Püf Noktaları ve İpuçları - Bkz. "Alt kabukların kullanımı NOT ..."


gerçekten ilginç ...
iruvar 5:14

"IFS ile dizilemek için Bash dizesi" IFS=$'\n' declare -a astr=(...)mükemmel teşekkürler!
Kova Gücü

1

Sorudan bu pasajı:

IFS=$'\n' a=($str)

soldan sağa değerlendirilen iki ayrı genel değişken ataması olarak yorumlanır ve buna eşdeğerdir:

IFS=$'\n'; a=($str)

veya

IFS=$'\n'
a=($str)

Bu, hem global'in neden IFSdeğiştirildiğini hem de $strdizi elemanlarına kelime bölünmesinin neden yeni değeri kullanılarak yapıldığını açıklar IFS.

Bu IFSgibi değişikliklerin etkisini sınırlandırmak için bir alt kabuk kullanmaya istekli olabilirsiniz :

str="value 0:value 1"
a=( old values )
( # Following code runs in a subshell
 IFS=":"
 a=($str)
 printf 'Subshell IFS: %q\n' "${IFS}"
 echo "Subshell: a[0]='${a[0]}' a[1]='${a[1]}'"
)
printf 'Parent IFS: %q\n' "${IFS}"
echo "Parent: a[0]='${a[0]}' a[1]='${a[1]}'"

ancak değişikliklerin aaynı zamanda alt kabukla sınırlı olduğunu hemen fark edeceksiniz :

Subshell IFS: :
Subshell: a[0]='value 0' a[1]='value 1'
Parent IFS: $' \t\n'
Parent: a[0]='old' a[1]='values'

Daha sonra, bu önceki cevabın çözümünü kullanarak @msw tarafından IFS'yi kaydetme / geri yükleme ya da @helpermethod tarafından önerilenlocal IFS bir işlevi kullanmayı denemeye özen gösterin. Ancak kısa süre sonra, özellikle her türlü sıkıntıda olduğunuzu fark edersiniz, özellikle de kötü niyetli senaryoları çağıranlara karşı sağlam olması gereken bir kütüphane yazarıysanız:

  • Ya IFSbaşlangıçta üzülmediyse?
  • Ya set -u(aka set -o nounset) ile koşuyorsak ?
  • Ya IFSsalt okunur aracılığıyla yapılırsa declare -r IFS?
  • Özyineleme ve / veya eşzamansız yürütme ile çalışmak için kaydetme / geri yükleme mekanizmasına ihtiyacım olursa ne olur trap?

Lütfen IFS'yi kaydetmeyin / geri yüklemeyin. Bunun yerine geçici değişikliklere sadık kalın:

  • Değişken değişikliklerini tek bir komutla, yerleşik veya işlev çağrısı ile sınırlandırmak için kullanın IFS="value" command.

    • Belirli bir karaktere bölerek birden çok değişkene okumak için ( :aşağıda örnek olarak kullanılır),

      IFS=":" read -r var1 var2 <<< "$str"
    • Dizi kullanımı için kullanın (bunun yerine bunu yapın array_var=( $str )):

      IFS=":" read -r -a array_var <<< "$str"
  • Değişkeni değiştirmenin etkilerini bir alt kabuğa sınırlayın.

    • Dizinin elemanlarını virgülle ayrılmış olarak vermek için:

      (IFS=","; echo "${array[*]}")
    • Bunu bir dizgede yakalamak için:

      csv="$(IFS=","; echo "${array[*]}")"

0

En yalındır çözüm, $IFSörneğin msw'nin yanıtında olduğu gibi orijinalin bir kopyasını almaktır . Bununla birlikte, bu çözüm birçok uygulama için önemli olan IFSbir IFSdizi ile boş dizeye eşit bir set arasında ayrım yapmaz . İşte bu ayrımı yakalayan daha genel bir çözüm:

# Functions taking care of IFS
set_IFS(){
    if [ -z "${IFS+x}" ]; then
        IFS_ori="__unset__"
    else
        IFS_ori="$IFS"
    fi
    IFS="$1"
}
reset_IFS(){
    if [ "${IFS_ori}" == "__unset__" ]; then
        unset IFS
    else
        IFS="${IFS_ori}"
    fi
}

# Example of use
set_IFS "something_new"
some_program_or_builtin
reset_IFS
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.