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"是两个字符反斜杠和小写字母 n。

现在,在某些 shell 中,echo "\n", 会打印一个换行符(实际上是两个),并且在所有 shell 中printf "\n"都会。但那是因为echoprintf特别对待反斜杠。这与 C、Perl 和 Python 等不同,在这些语言中,反斜杠转义符总是在(双引号)字符串中进行特殊处理。

在许多 shell 中,$'\n'将是一个包含换行符的字符串,仅此而已。 (但在纯 POSIX sh 中,您需要在引号内添加文字换行符。)

也就是说,在 Bash/Ksh/Zsh 中,您可以仅根据正则表达式测试字符串,而不是手动循环:

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

for (( .. ))并且${string:i:j}也不是标准的 POSIX 功能,并且不能在 Ubuntu 所具有的 Dash 等中工作/bin/sh。)

答案2

正如其他人指出的那样,"\n"不是换行符,只是一个带有\和 的字符串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

或者,在标准shshell 中:

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)来执行除最琐碎的字符串或文本处理之外的任何操作。看为什么使用 shell 循环处理文本被认为是不好的做法?出于某些原因。

循环遍历多行字符串的每个字符绝对不应该单独在 shell 中完成。它会非常慢,并且您会遇到各种引用和行尾标记(例如\n)的问题,并且大多数版本的 sh(除了 bash、ksh 和 zsh)甚至没有对正则表达式的内置支持 - 在我看来,这是文本处理所需的最低功能。

相反,使用awkperl或其他一些文本处理实用程序/语言。

例如,您可以将整个 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 中使用的正则表达式都是/^[ #]*$/!.这匹配任何不只包含空格或散列的行。

它们三个都在确认良好输入时返回退出代码 0,在不匹配时返回 1。这可以在 sh 中使用$?变量进行测试:

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

您还可以使用一个简单的 GNUsed脚本:

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

相关内容