![bash:循环字符不起作用](https://linux22.com/image/14476/bash%EF%BC%9A%E5%BE%AA%E7%8E%AF%E5%AD%97%E7%AC%A6%E4%B8%8D%E8%B5%B7%E4%BD%9C%E7%94%A8.png)
我有这段代码检查字符串是否仅包含空格和哈希字符(“#”),如果是,则回显“是”,如果不回显“否”
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"
都会。但那是因为echo
并printf
特别对待反斜杠。这与 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
或者,在标准sh
shell 中:
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)甚至没有对正则表达式的内置支持 - 在我看来,这是文本处理所需的最低功能。
相反,使用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 中使用的正则表达式都是/^[ #]*$/
用!
.这匹配任何不只包含空格或散列的行。
它们三个都在确认良好输入时返回退出代码 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