IFS(内部フィールド区切り文字)は、複数の連続する区切り文字の単一の区切り文字として機能できますか?


10

ホワイトスペース以外の値を持つIFSを使用して配列を解析すると、空の要素が作成されます。複数のdelimを1つのdelimに縮小するために
使用tr -sするだけでも十分ではありません。
例では、問題をより明確に説明でき
ます。IFSの微調整を介して「通常の」結果を達成する方法はありますか(IFSの動作を変更するための関連設定はありますか?....つまり、デフォルトの空白と同じように動作するには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>"

これが出力です


============== 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>

同じ質問に対するより良い(私は思う)回答があります:stackoverflow.com/a/14789518/1765658
F. Hauri

回答:


3

複数の(スペースではない)連続する区切り文字を削除するには、2つの(文字列/配列)パラメータ拡張を使用できます。トリックはIFS、配列パラメーターの展開のために変数を空の文字列に設定することです。

これは、Word Splittingに記載さman bashれています

値を持たないパラメーターの展開から生じる、引用符で囲まれていない暗黙のnull引数は削除されます。

(
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
)

良い!シンプルで効果的な方法-bashループやユーティリティアプリを呼び出す必要はありません— BTW。あなたが述べたように、「(非空白)」、私はそれがスペースを含む、区切り文字の任意の組み合わせで正常に動作することを、明確にするため、指摘したいです。
Peter.O

私のテストでは、設定IFS=' '(つまり空白)は同じように動作します。これは、の明示的なnull引数( ""または '')よりも混乱が少ないと思いますIFS
Micha Wiedenmann、2015

データに空白が含まれている場合、これはひどい解決策の1つです。これは、データが「abc」ではなく「a bc」の場合、IFS = ""は「a」を「bc」とは別の要素に分割します。
Dejay Clayton 2015

5

bashマンページから:

IFSの空白ではないIFSの任意の文字と、隣接するIFSの空白文字によってフィールドが区切られます。IFSの空白文字のシーケンスも区切り文字として扱われます。

これは、IFSの空白(スペース、タブ、および改行)が他の区切り文字と同様に扱われないことを意味します。代替のセパレータでまったく同じ動作をしたい場合は、trorを使用して、セパレータの入れ替えを行うことができます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

これ%#%#%#%#%は、フィールド内の可能なスペースを置き換える魔法の値であり、「一意」(または非常にリンクなし)であることが期待されています。フィールドにスペースがないことが確実な場合は、この部分をドロップしてください)。


@FussyS ...ありがとう(私の質問の変更を参照してください)...あなたは私の意図した質問への答えを私に与えたかもしれません..そしてその答えは(おそらくそうです)「IFSを動作させる方法はありませんtr問題を示すために例を意図して います...システムコールを避けたいので${var##:}、glenの回答者へのコメントで述べたもの以外のbashオプションを見ていきます。 。しばらくお待ちください..多分IFSを同軸化する方法があるかもしれませんが、そうでなければ、あなたの答えの最初の部分は後にありました...
Peter.O

その処理はIFSすべてのBourneスタイルのシェルで同じで、POSIXで指定されています。
Gilles「SO-邪悪なことをやめなさい」

私がこの質問をしてから4年以上が経過しました。@ nazadの回答(1年以上前に投稿されたもの)はIFS、区切り文字列として任意の数と文字の組み合わせを含む配列を作成するIFSを操作する最も簡単な方法であることがわかりました。私の質問はによって最もよく回答されましたjon_dが、@ nazadの回答は、IFSループやユーティリティアプリを使用せずに使用する気の利いた方法を示しています。
Peter.O

2

bash IFSは、連続する区切り文字を単一の区切り文字(空白以外の区切り文字の場合)として扱う社内の方法を提供しないため、すべてのbashバージョンをまとめました(vs.外部呼び出し、たとえば、tr、awk、sedを使用)。 )

マルチチャーIFSを処理できます。

このQ / Aページに示されているtrawkオプションの同様のテストとともに、その実行時間の結果を以下に示します...テストは、アレイを作成するだけの10000回の反復に基づいています(I / Oなし)...

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

これが出力です

# 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"

これがスクリプトです

#!/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

すばらしい仕事、興味深い+1!
F.ハウリ

1

あなたもgawkでそれを行うことができますが、それはきれいではありません:

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

出力

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

おかげで...私は私の主な要求(修正問題)で明らかにされていないように見える...それはちょうど私変更することでそれを行うための簡単な十分だ$var${var##:}微調整するための方法は、自分自身をIFS後...私は..私はしたい本当にでした外部呼び出しなしでこれを行うには(私はbashがこれを外部のどの缶よりも効率的に行うことができると感じています...したがって、私はそのトラックを続けます)...あなたのメソッドは機能します(+1)...入力の変更が進むにつれて、awkやtrではなくbashで試してみたいと思います(システムコールを回避します)が、実際にIFSの調整に
時間を費やしてい

前述のように、@ fredは、IFSがデフォルトの空白値の連続する複数のデリミタのみをスローアップします。それ以外の場合、区切り文字が連続すると、無関係な空のフィールドが生成されます。1つまたは2つの外部呼び出しが実際の方法でパフォーマンスに影響を与える可能性は非常に低いと思います。
グレン・ジャックマン、2011

@glen ..(あなたの答えは「かなり」ではないと言いました..私はそれがそうだと思います!:)しかし、私はすべてのbashバージョン(外部​​呼び出しに対する)をまとめ、アレイを構築するだけの10000反復に基づいています( I / Oなし)... bash 1.276s... call (awk) 0m32.210s,,, call (tr) 0m32.178s...数回実行すると、bashが遅いと思うかもしれません!...この場合awkの方が簡単ですか?...すでにスニペットを取得している場合はそうではありません:) ...後で投稿します。今行かないと。
Peter.O

ちなみに、あなたのgawkスクリプトを再...私は以前にawkを基本的に使用したことがないので、詳細に(そして他の人を)調べてきました...理由は特定できませんが、引用されたデータを与えられたときに問題がとにかく..それは、テストデータを引用符の間のスペースでの引用符、および分割を失い...と引用符の奇数のためにクラッシュした...ここにあります:var="The \"X\" factor:::A single '\"' crashes:::\"One Two\""
Peter.O

-1

簡単な答えは、すべての区切り文字を1つ(最初)に折りたたむことです。
これにはループが必要です(実行log(N)回数が少ない):

 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

あとは、文字列を1つの区切り文字で正しく分割して出力するだけです。

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

set -fIFSを変更する必要はありません。
スペース、改行、グロブ文字でテストされています。すべての仕事。かなり遅い(シェルループが予想されるため)。
ただし、bashの場合のみ(readarrayのオプションのため、bash 4.4 -d以降)。


sh

シェルバージョンは配列を使用できません。使用可能な配列は位置パラメータのみです。
使用tr -sは1行のみです(IFSはスクリプトで変更されません)。

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

そしてそれを印刷します:

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

まだ遅いですが、それ以上ではありません。

コマンドcommandはボーンでは無効です。
zshでは、command外部コマンドのみを呼び出し、commandを使用するとevalが失敗します。
kshでは、を使用してもcommand、グローバルスコープでIFSの値が変更されます。
またcommand、mksh関連のシェル(mksh、lksh、posh)で分割が失敗しcommandます。コマンドを削除すると、より多くのシェルでコードが実行されます。ただし、削除するcommandと、IFSは、bash(posixモードなし)とデフォルト(エミュレーションなし)モードのzshを除いて、ほとんどのシェル(evalは特別な組み込み)でその値を保持します。この概念は、デフォルトのzshの有無にかかわらず機能しませんcommand


複数文字IFS

はい、IFSは複数の文字にすることができますが、各文字は1つの引数を生成します。

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

出力されます:

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

bashを使用すると、commandsh / POSIXエミュレーションでない場合は単語を省略できます。コマンドはksh93で失敗します(IFSは変更された値を保持します)。zshでは、このコマンドcommandはzshにeval外部コマンド(見つからない)としての検索を試行させ、失敗します。

何が起こるかというと、1つの区切り文字に自動的に縮小されるIFS文字は、IFSの空白のみです。
IFSの1つのスペースは、連続するすべてのスペースを1つに縮小します。1つのタブですべてのタブが折りたたまれます。1つのスペース 1つのタブは、一連のスペースやタブを1つの区切り文字に折りたたみます。改行でアイデアを繰り返します。

複数の区切り文字を折りたたむには、いくつかのジャグリングが必要です。
ASCII 3(0x03)が入力で使用されていないと仮定します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、およびbash(about commandおよびIFS)に関するコメントのほとんどは、ここでも適用されます。

の値は$'\0'テキスト入力ではあまりありませんが、bash変数にNUL(0x00)を含めることはできません。

shには、同じ文字列操作を行う内部コマンドがないため、shスクリプトの唯一のソリューションはtrです。


はい、私はOPが要求したシェル用にBashと書きました。そのシェルではIFSは保持されません。そして、はい、たとえばzshに移植できません。@StéphaneChazelas–
Isaac

SHとして呼び出されたときのbashとzshの場合には、彼らは、POSIXの指定として振る舞う
ステファンChazelasを

@StéphaneChazelas各シェルの制限に関する(多くの)メモを追加しました。
Isaac

@StéphaneChazelasなぜ反対票か
Isaac

分からない、私じゃなかった。ところで、私はここでおよそ専用Q&Aがあると思いcommand evalジルによってIIRC
ステファンChazelas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.