一致する{}でカンマを無視し、カンマで区切られたリストを分割します。

一致する{}でカンマを無視し、カンマで区切られたリストを分割します。

CSVを分割したいのですが、中かっこグループ内のカンマ一致を無視して各リストメンバーを繰り返します。以下のコードはうまく機能しますが、中かっこグループ内のコンマを考慮しません。

仮定:

  • ここにありますいつも一致する支柱のペア。つまり、 {{ {a,b,c}, xwillのような入力いいえ発生する。

予想出力:

Word='{0,1}'
Word='alpha'
Word='{(x,y,z)}'
Word='{{1,2,3}, {a,b,c}}'

引用:

パスワード:

#!/bin/bash

#TEST_STRING="alpha, beta, gamma" ## <--- works great for simple case
TEST_STRING="{0,1}, alpha, {(x,y,z)}, {{1,2,3}, {a,b,c}}"

echo "${TEST_STRING}" | sed -n 1'p' | tr ',' '\n' | while read Extracted_Word; do
    printf "Word='%s'\n" "${Extracted_Word}"
done

私は123の(現在削除された)ソリューションを適用してみました。

#!/bin/bash

#TEST_STRING="alpha, beta, gamma" ## <--- works great for simple case
TEST_STRING="{0,1}, alpha, {(x,y,z)}, {{1,2,3}, {a,b,c}}"

echo "${TEST_STRING}" \
    | sed -n 1'p' \
    | sed 's/\({[^}]*\({[^}]*}[^}]*\)*} *\)\(,\|$\) */\1\n/g;:1;s/\(\n[^{}]*\), */\1\n/;t1' \
    | tr ',' '\n' \
    | while read Extracted_Word; do
    printf "Word='%s'\n" "${Extracted_Word}"
done

ただし、これにより、次のエラーメッセージが生成されます。

./testcsv.sh
sed: 1: "s/\({[^}]*\({[^}]*}[^}] ...": bad flag in substitute command: ':'
./testcsv.sh: line 18: {{ {a,b,c}, x: command not found

答え1

純粋な試み強く打つ

#!/bin/bash
TEST_STRING="{0,1}, alpha, {(x,y,z)}, {{1,2,3}, {a,b,c}}"
TEST_STRING="$TEST_STRING"","
count=0
newword=''
while [ "${TEST_STRING::1}" ] ; do 
    l="${TEST_STRING::1}"
    TEST_STRING=${TEST_STRING:1}
    [ "$l" = '{' ] && ((count++))
    [ "$l" = '}' ] && ((count--))
    if [ "$l" = ',' ] && ! ((count)) ; then
        echo "Word='$newword'"
        newword=''
    else
        if [ "$newword" ] || [ "$l" != " " ] ; then
            newword="$newword""$l"
        fi
    fi
done

答え2

以下は、サンプルを分割するsedスクリプトです。

#!/bin/sed -Ef

# replace all commas with newlines
s/,/\
/g

# Do we need to re-join any lines?
:loop
# Unmatched brace containing possibly another (matched) level of
# braces:
s/(\{([^{}]|\{[^{}]*\})*)\
/\1,/
tloop

# remove any leading space
s/\n */\
/g

# At first line, print result, then exit.
1q

警告:質問の説明に従って、2つのレベルの中かっこを処理します。

テスト:

$ ./259252.sed <<<'{0,1}, alpha, {(x,y,z)}, {{1,2,3}, {a,b,c}}'
{0,1}
alpha
{(x,y,z)}
{{1,2,3}, {a,b,c}}

最初の行を処理した後に終了することを示します。

$ ./259252.sed <<<$'a,b,c\nd,e,f'
a
b
c

私はこれをLinuxで実行し、次の答えを使用しています。Mac OSXのsedと他の「標準」sedの違いは何ですか?MacOSに移植してください。これがうまくいかない場合この回答brew install gnu-sedsedを使用してGNUをインストールした後に呼び出すgsed代わりに使用することをお勧めします。sed

使用中:

#!/bin/bash

TEST_STRING="{0,1}, alpha, {(x,y,z)}, {{1,2,3}, {a,b,c}}"

echo "${TEST_STRING}" | sed -E -f 259252.sed | while read Extracted_Word; do
    printf "Word='%s'\n" "${Extracted_Word}"
done

これは作る:

Word='{0,1}'
Word='alpha'
Word='{(x,y,z)}'
Word='{{1,2,3}, {a,b,c}}'

答え3

str='{0,1},alpha,{(x,y,z)},{{1,2,3},{a,b,c}}'
OPTIND=1 l=0 r=0; set ""
while   getopts : na -"$str"
do      [ "$l" -gt "$r" ]
        case    $?$OPTARG  in
        (1,)  ! l=0 r=0    ;;
        (0})    r=$((r+1)) ;;
        (?{)    l=$((l+1)) ;;
        esac    &&
        set -- "$@$OPTARG" ||
        set -- "$@" ""
done;   printf  %s\\n "$@"

dashバグがあり、次のものが必要です。

set -- "$@" ""; str=${str#?}

...しかし、それ以外は上記の作業は非常に速く、基本的にすべてのPOSIXシェルで動作し、非常に簡単でなければなりません。一致しないペアも処理する必要があります。(必ずしも必要なくても)}前の項目の前に表示されるaを無視して特別に認識します{


{0,1}
alpha
{(x,y,z)}
{{1,2,3},{a,b,c}}

プレフィックス文字列と周辺引用符を取得するには、次のように置き換えることができます。

printf "Word='%s'\n" "$@"

...printf %s\\n "$@"上で使用されています。ここに例の値が与えられると、$str次のものが印刷されます。

Word='{0,1}'
Word='alpha'
Word='{(x,y,z)}'
Word='{{1,2,3},{a,b,c}}'

あなたはもっと決心したかもしれません...

for W do alias "Word=$W" Word; done

...結果は...

Word='{0,1}'
Word=alpha
Word='{(x,y,z)}'
Word='{{1,2,3},{a,b,c}}'

...必要に応じて引用し、埋め込まれた二重引用符も正しく引用します。(しかしを使用している場合は、bashまずこれを行う必要があるかもしれませんset --posix

だからデモをしようとすると...

str="{0,1

}}, {,}alph}'a, {(x,y,z)}, {{1,2,3}, {a,b,c}}" 
OPTIND=1 l=0 r=0; set ""
while   getopts : na -"$str"
do      [ "$l" -gt "$r" ]
        case    $?$OPTARG  in
        (1,)  ! l=0 r=0    ;;
        (0})    r=$((r+1)) ;;
        (?{)    l=$((l+1)) ;;
        esac    &&
        set -- "$@$OPTARG" ||
        set -- "$@" ""
done;   for W do alias "Word=${W# }" Word
done

Word='{0,1

}}'
Word='{,}alph}'\''a'
Word='{(x,y,z)}'
Word='{{1,2,3}, {a,b,c}}'

...先行スペース処理も非常に簡単です...

答え4

別のbashソリューション:

  • 一致しない中括弧のペアを処理します{
  • 1つ以上の開かれた括弧が表示されるまで、閉じた中括弧は許可されません。
  • 行末で中括弧の数をゼロにリセットします。
  • 開かれた中かっこよりも閉じる中かっこが多い場合、カンマは有効なカンマとして許可されます。
  • ソリューションの前のスペースは削除されます。
  • 結果の単語が引用されます。

パスワード:

str="}}{0,1}}, {,}alph}'a"

            fin='false' d='0'
until  $fin
do     IFS=   read -r -d '' -n 1 a || fin='true'
       if     [[ $a == '{' ]] ; then (( d++ )) ; fi ### count openning braces.
       if     [[ $a == ',' ]] && (( d<1 )) || $fin  ### ',' out of braces or end.
       then   $fin && s="${s%$'\n'}"                ### removing a last newline.
              set -- "$@" "$s"                      ### store in an array.
              unset a s d                           ### unset working variables.
       fi
       if [[ $a == '}' ]] && ((d>0)); then ((d--)); fi  ### close braces.
       s="$s$a"
done <<<"$str"
printf 'Word=%q\n' "${@# }"       ### print a quoted value removing front space.

出力:

Word=\}\}\{0\,1\}\}
Word=\{\,\}alph\}\'a

またはもう少し不思議なこと:

str="{0,1

}}, {,}alph}'a, {(x,y,z)}, {{1,2,3}, {a,b,c}}"

        fin='false' d='0'
until  $fin
do     IFS=   read -r -d '' -n 1 a || fin='true'
       [[ $a == '{' ]] && (( d++ ))                 ### count openning braces.
       [[ $a == ',' ]] && (( d<1 )) || $fin && {    ### ',' no braces (or end).
              $fin && s="${s%$'\n'}"                ### removing a last newline.
              set -- "$@" "$s"                      ### store in an array.
              unset a s d                           ### unset working variables.
       }
       [[ $a == '}' ]] && (( d>0 )) && ((d--))      ### substract closing braces.
       s="$s$a"
done <<<"$str"
printf 'Word=%q\n' "${@# }"    ### print a quoted value with front space removed.

結果:

Word=$'{0,1\n\n}}'
Word=\{\,\}alph\}\'a
Word=\{\(x\,y\,z\)\}
Word=\{\{1\,2\,3\}\,\ \{a\,b\,c\}\}

関連情報