文字列からn番目の文字を抽出する

文字列からn番目の文字を抽出する

私は解決策を見つけようとしていますこれ質問です。これまでのところ、この問題に対する私のアプローチは次のとおりです。

  • すべての文字を連結して長い文字列にします。
  • 上記の手順の後、空白またはタブスペースをすべて削除して、1 つの大きな文字列だけになるようにします。

上記の手順を以下のコマンドで実行できました。

column -s '\t' inputfile | tr -d '[:space:]'

このような入力ファイルの場合、

1   0   0   0   0   0

0   1   1   1   0   0

上記のコマンドを適用すると、値は次のようになります。

100000011100

現在、この大きな文字列では、以下のようなアプローチを適用しようとしています。

6 文字ごとに抽出し(元の OP の希望どおり)、文字列の末尾まで配列要素に追加します。

つまり、基本的に上記の手順では、配列要素を次のように作成しようとしています。

10(1番目と 7番目の文字)、01(2番目と 8番目の文字)、01(3番目と 9番目の文字)、01(4番目と 10番目の文字)、00(5番目と 11番目の文字)、00(6番目と 12番目の文字)。

そこで私の質問は、n番目の文字ごとに抽出して、配列に追加し、さらに処理を進めるにはどうすればよいかということです(この場合は n = 6)。

答え1

2行

bash以下は配列を生成する純粋なソリューションですbash

s="100000011100"
array=($(
    for ((i=0; i<${#s}-6; i++))
    do
        echo "${s:$i:1}${s:$((i+6)):1}"
    done
    ))
echo "${array[@]}"

これにより、質問に示されているものと同じ出力が生成されます。

10 01 01 01 00 00

ここで重要な要素はbashの使用です部分文字列の拡張parameterBash では、 を介して、たとえば変数から部分文字列を抽出できます${parameter:offset:length}。この場合、オフセットはループ変数によって決定されi、長さは常に です1

任意の行数に対する一般的なソリューション

たとえば、元の文字列に 18 文字あり、0 から 5 までの i 番目、i+6 番目、i+12 番目の文字を抽出したいとします。その場合、次のようになります。

s="100000011100234567"
array=($(
    for ((i=0; i<6; i++))
    do
        new=${s:$i:1}
        for ((j=i+6; j<${#s}; j=j+6))
        do 
            new="$new${s:$j:1}"
        done
        echo "$new"
    done
    ))

echo "${array[@]}"

出力は次のようになります:

102 013 014 015 006 007

この同じコードは、任意の数の 6 文字の行に拡張されます。たとえば、s次のコードが 3 行 (18 文字) の場合:

s="100000011100234567abcdef"

すると、出力は次のようになります。

102a 013b 014c 015d 006e 007f

答え2

使用方法perl:

$ echo 100000011100 | perl -nle '
    for ($i = 0; $i < length()/2; $i++) {
        print substr($_,$i,1), substr($_,$i+6,1);
    }
'
10
01
01
01
00
00

これは 2 行で動作します。任意の行で動作させたい場合は、大きな文字列を構築するのではなく、行を直接処理する必要があります。次の入力では:

1   0   0   0   0   0                                                           
0   1   1   1   0   0                                                           
0   0   0   0   0   0

試す:

$ perl -anle '
    for ($i = 0; $i <= $#F; $i++) {
      push @{$h{$i}}, $F[$i];
    }
    END {
        print @{$h{$_}} for keys %h;
    }
' file
000
010
000
100
010
010

答え3

シェルソリューションとしては、getoptsおそらく最も簡単です。POSIXgetopts仕様では、まさにあなたが求めていること、つまりシェルループでバイトストリームを処理することが求められています。奇妙に聞こえるかもしれませんが、私がこれを学ぶ前の私と同じように、おそらく次のように考えていたでしょう。まあ、コマンドラインスイッチを処理するものだと思っていました。それは真実ですが、最初のことも真実です。次のことを考慮してください。

-thisisonelongstringconsistingofseparatecommandlineswitches

はい、getoptsそれを処理する必要があります。ループで文字ごとに分割し、$OPTARG呼び出し時にどの程度詳細に指定したかに応じて、シェル変数または名前で指定した別の変数に各文字を返す必要があります。さらに、シェル変数にエラーを返す必要があり、進捗状況を保存するシェル変数でそれが実行さ$OPTINDれると、中断したところから再開する何らかの方法で対処できる場合、サブシェルを 1 つも呼び出さずにすべての作業を実行する必要があります。

たとえば、次のようになります。

arg=$(seq -s '' 1000); set --
while getopts :0123456789 v -"${arg}"
do [ "$((i=$i+1<6?$i+1:0))" -gt 0 ] ||
set "$@" "$v"
done

うーん....効いたかな?

echo "$((${#arg}/6))" "$#"
482 482

それはすばらしい...

eval '
printf %.1s\\n "${arg#'"$(printf %0$((124*6-1))d | tr 0 \?)"'}" "${124}"'
4
4

ご覧のとおり、getoptsコマンドは文字列の 6 バイトごとに配列を完全に設定します。また、これはこのように数値である必要はなく、シェル セーフ文字である必要もありません。また、上記で行ったようにターゲット文字を指定する必要もありません01234565789。私はこれを多くのシェルで繰り返しテストしましたが、すべて正常に動作しました。いくつかの癖があります。bash最初の文字が空白文字の場合はそれを破棄します。POSIX で明示的に禁止されている唯一の文字であるコロンも、指定されたパラメーターとしてdash受け入れます。ただし、エラーが返されても、現在の opt 文字の値が引き続き格納される:ため、これらのことは問題になりません。getopts$OPTARG(指定した opt 変数に割り当てられた ? で表されます)それ以外の場合は、$OPTARGオプションに引数が必要であると宣言しない限り、明示的に設定を解除します。そして、空白文字は良いことです。リードするスペースは優れています。未知の値を扱うときに、次の操作を実行できるためです。

getopts : o -" $unknown_value"

...最初の文字が実際に受け入れられた args 文字列に含まれる危険なしにループを開始します。その結果、getopts全体が$OPTARG引数として一度に組み込まれることになります。

もう一つの例を挙げます。

OPTIND=1
while getopts : o -" $(dd if=/dev/urandom bs=16 count=1 2>/dev/null)"                         
do printf '\\%04o' "'$OPTARG"; done  

\0040\0150\0071\0365\0320\0070\0161\0064\0274\0115\0012\0215\0222\0271\0146\0057\0166

$OPTIND=1最初の行で設定したのは、使用したばかりgetoptsで、リセットするまで、次の呼び出しが中断したところから続行されることを期待しているからです。言い換えると、それが望んでいることです。しかし、私は与える気がなく、今は別のことをしているので、どの時点でリセットしてよいかを"${arg2}"知らせます。$OPTIND

この例で私が使ったのはzsh、先頭のスペースについて文句を言わない、つまり最初の文字が8進数の40であるスペース文字です。getoptsしかし、私は通常そのようには使いません。避ける各バイトに対してを実行しwrite()、代わりにその出力 (変数に入っている) を別のシェル変数に割り当てます (上でsetafter で行ったのと同じ方法です)。その後、準備ができたら文字列全体を取得し、通常は最初のバイトを削除します。

答え4

sed私の頭に最初に浮かぶのはこれです。

$ echo 1234567890abcdefghijklmnopqrstuvwxyz | sed 's/.\{5\}\(.\)/\1/g'
6bhntz

5 つの文字を一致させ、6 番目の文字をキャプチャし、キャプチャした文字ですべてを置き換えます。

ただし、文字列の長さが 6 の正確な倍数でない場合は問題が発生します。

$ echo 1234567890abcdefghijklmnopqrstuvwxy | sed 's/.\{5\}\(.\)/\1/g' 
6bhntuvwxy

sedしかし、少し変更することでこれを修正できます。

$ echo 1234567890abcdefghijklmnopqrstuvwxy | sed 's/.\{1,5\}\(.\{0,1\}\)/\1/g'
6bhnt

正規表現の貪欲な性質により、可変長の一致は可能な限り一致し、キャプチャするものが何も残っていない場合はキャプチャされず、文字が削除されるだけです。

関連情報