bash:繰り返し文字が機能しません

bash:繰り返し文字が機能しません

文字列にスペースとハッシュ文字( "#")のみが含まれていることを確認するこのコードがあります。そうであれば「はい」をエコーし​​、そうでなければ「いいえ」をエコーし​​ます。

string="###############
        #             #
        # #############
        #             #
        # ######### # #
        #         # # #
        # ### ##### # #
        #   #     # # #
        # # ###########
        # #            
        ###############"
                       
 confirm_variable="Yes"
 for (( i=0; i<${#string}; i++ )); do
    str="${string:$i:1}"
    if [ "$str" == "#" ] || [ "$str" == " " ] || [ "$str" == "\n" ] ; 
        then
            continue
        else
            confirm_variable="No"
            break
    fi

done
echo $confirm_variable

なぜこれがうまくいかないのかわかりません。文字列を次のようにするのと同じです。

string="## #### # #  #" 

とてもうまくいくようです。

答え1

"\n"2文字のバックスラッシュと小文字のn。

一部のシェルではecho "\n"改行文字(実際には2つ)を印刷し、すべてのシェルではを印刷しますprintf "\n"。しかし、これはバックスラッシュが特別に扱われるからですechoprintfこれは、バックスラッシュエスケープが常に(二重引用符で囲まれた)文字列で特別に扱われるC、Perl、Pythonなどの言語とは異なります。

多くのシェルでは、$'\n'これは改行文字のみを含む文字列です。 (しかし、純粋なPOSIX shでは、引用符の中にリテラル改行文字を追加する必要があります。)

つまり、Bash/Ksh/Zsh では手動で繰り返すのではなく、正規表現の文字列をテストできます。

re=$'^[# \n]*$'
if [[ $string =~ $re ]]; then
    echo "string contains only #, space and newline"
fi

(これは標準のPOSIX機能ではなくfor (( .. ))${string:i:j}UbuntuのDashなどでは機能しません/bin/sh。)

答え2

他の人が指摘したように、"\n"改行文字ではなく\aと文字列がありますn

文字列にスペース、ハッシュ、および改行のみが含まれているかどうかを検索するには、個々の文字を繰り返すのではなく、パターンマッチングを使用します。

次のことができます。

case $string in
    (*[![:space:]#]*)
        echo 'other characters in string'
        ;;
    (*)
        echo 'only space-like characters or hashes in string'
esac

または次のようになります(bash)。

if [[ $string == *[![:space:]#]* ]]; then
        echo 'other characters in string'
else
        echo 'only space-like characters or hashes in string'
fi

ここで使用するのは、[:space:]空白、タブ(さまざまな種類)、キャリッジリターン、改行など、さまざまな空白に似た文字に一致するPOSIX文字クラスです。このパターンは、*[![:space:]#]*次の文字を含むすべての文字列と一致します。いいえスペースなどの文字または#

タブやキャリッジリターンを許可しないなど、より制限的に適用するには、$'*[! \n#]*'次のパターンを使用してくださいbash

pattern=$'*[! \n#]*'
if [[ $string == $pattern ]]; then
        echo 'other characters in string'
else
        echo 'only spaces, hashes, or newlines in string'
fi

または、標準shシェルで次の操作を行います。

pattern='*[!
 #]*'

case $string in
    ($pattern)
        echo 'other characters in string'
        ;;
    (*)
        echo 'only spaces, hashes, or newlines in string'
esac

答え3

最もマイナーな文字列やテキスト操作以外の操作を実行するには、sh(通常のshからbash、ksh、またはzshまでのすべてのバリアント)を使用しないでください。バラよりシェルループを使用してテキストを処理するのはなぜ悪い習慣と見なされますか?いくつかの理由。

複数行文字列の各文字の繰り返しは、シェルで個別に実行してはいけません。非常に遅く、引用符や行末のすべての種類の表示(例:)\nに問題があり、ほとんどのshバージョン(bash、ksh、zshを除く)には組み込み機能もありません。正規表現のサポート - 私の考えでは、これはテキスト処理に必要な最小限の機能です。

代わりに、他のテキスト処理ユーティリティ/言語をawk使用してください。perl

たとえば、for ループ全体を次のように置き換えることができます。

echo "$string" | perl -lne 'BEGIN {$c="Yes"; $ec=0};
                            if (!m/^[ #]+$/) { $c="No"; $ec=1; last };
                            END {print $c; exit $ec}'

または

echo "$string" | awk -v c="Yes" -v ec=0 \
                     '!/^[ #]*$/ { c="No"; ec=1; nextfile };
                      END { print c; exit ec}'

これにはGNU awk(または他のサポートされているawk nextfile)が必要です。他のバージョンのawkでは、次のコードが機能しますが、一致エラーが発生した場合はすぐにループを終了しないため、少し遅くなります。

echo "$string" | awk -v c="Yes" -v ec=0 \
                     '!/^[ #]*$/ { c="No"; ec=1 };
                      END { print c; exit ec}'

明らかに、これらは同じスクリプトであり、他のPerlとawkの構文に対して少し異なるように書き直されました。

上記の正規表現はperlとawkの両方で使用されます/^[ #]*$/!これは、空白やハッシュだけを含まないすべての行と一致します。

3 つすべて正しい入力が確認された場合は終了コード 0 を返し、一致するものがない場合は 1 を返します。これはshの変数を使って$?テストできます。

if [ $? -eq 1 ] ; then
   : # string has bad characters, do something!
fi

単純なGNUスクリプトを使用することもできますsed

echo "$string" | sed -n -e '/[^ #]/q1'
if [ $? -eq 0 ] ; then echo "Yes" else echo "No" ; fi

(quit)コマンドの終了コードqはsedのGNU拡張であるため、GNU sedが必要です。

または@Isaacが指摘したように、grep's -q(または--quiet/ --silent)オプションを使用することもできます。これにより、通常の出力が抑制され、終了コードのみが返されます。たとえば、

echo "$string" | grep -q '[^ #]'
if [ $? -eq 0 ] ; then echo "Yes" else echo "No" ; fi

関連情報