正如我在其他线程中已经解释过的While 语句:无法使多个条件复合起作用我正在尝试以某种方式解析输入参数recursive descent
。
可悲的是,尽管该帖子的参与者比我知识渊博并提供了很好的建议,但问题仍然存在。
我还在控制台中进一步探索了自己,但没有成功。
我的脚本中有以下两条while
语句,这是有问题的,因为显然该endOfInput
函数对结束 while 循环没有影响。
我会四处查看,有时它甚至不会进入循环。
所以外层while-statement
是这样的:
while [ $current_token_index -le $ARGC ] && [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do
另外,再一次,这是内部while-statement
while [[ "$current_token_index" -le "$ARGC" ]] && [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do
还有辅助方法:
isWord? () {
local pattern="^[a-zA-Z0-9_-]+$"
if [[ $1 =~ $pattern ]]; then
echo true
else
echo false
fi
}
isVersionNumber? () {
local pattern="^[0-9]{1,2}(\.[0-9]{,2})*$"
if [[ $1 =~ $pattern ]]; then
echo true
else
echo false
fi
}
EO_ARGS=false
function endOfInput? {
if [ $current_token_index -ge $ARGC ]; then
EO_ARGS=true
fi
echo $EO_ARGS
}
此外,该eat!
函数还可以从位置副本中获取下一个标记
eat! () {
current_token=${ARGV[$current_token_index]}
((current_token_index += 1))
current_char=${current_token:0:1}
}
除此之外我声明
# as set by eat!()
current_token=""
current_char=""
current_token_index=0
curent_char_index=0
我的问题指的是这种可能性,endOfInput
( 最初:endOfInput?
但?
据我所知,我删除了 ,在 bash 中它具有通配符含义,可能会导致问题。在 Ruby 中,您可以选择大多数特殊字符作为标识符,没有问题。显然有如果您来自其他编程语言,那么 bash 中会有很多警告)...因此,endOfInput
通过函数的多个定义以及 while 语句中的不同测试或比较语法,无法正确评估函数的真值及其自身的评估迫切性无论条件是否成立。因此,方程中的三件事共同负责条件星座的制定。
上面发布的循环头的问题while
是它们要么没有进入,要么没有停止。取决于我如何测试endOfInput
或如何分组。
如果我! endOfInput && ...
仅替换为,! false && ...
例如将进入 while 循环,否则不会进入。
所以我猜测肯定是函数有问题。它没有被正确评估。问题可能是 1) 的定义endOfInput
,或 2) 如何测试它,即,
2.1 与什么测试(例如
-eq
,字符串比较=
,算术运算符等==
)2.2什么经测试。 IE
- 2.2.1 字符串“true”/“false”
- 2.2.2
true
和false
作为文字 - 2.2.3
0
为true
与1
为false
3 这个值如何正确返回?
- 3.1 通过
return
- 3.2 通过
exit
- 3.3 通过
echo
- 3.1 通过
4 通过什么测试结构将返回值与其他值进行比较?
- 4.1 无,只执行函数
- 4.2 括号(
[
或[[
命令) - 4.3 算术括号
((...))
- 4.4 算术展开
$((...))
- 4.5 一项或多项的组合
所以请看一下 的定义endOfInput
以及它在语句头部的使用方式while
。可能是什么问题,为什么会出现无限循环?我的意思是,其他两个功能确实isWord?
有效。
我的函数定义应该如何endOfInput
,以及它在语句中的测试和串联应该如何,while
以便正确评估?
编辑:ilkkachu 希望我发布一个“最小、完整且可验证的示例”
我希望以下内容就足够了。
首先,我调用get_installed_gems_with_versions
以获取关联数组中的所有已安装的 gem 及其版本。现在我有了全局关联数组installedGems
。
那我想解析选项 --gems 通过调用parse_gems_with_versions
,依次调用parseGemVersions
以将已安装的 gems 及其版本的选择解析为$chosenGemVersions
我放弃了一些与解析部分相关的测试代码,但如果 while 循环不工作,则与当前问题无关。
所以这是代码
get_installed_gems_with_versions () {
unset installedGems
declare -Ag installedGems
local last_key=""
local values=""
while read -r line; do
line=${line##*/}
KEY="${line%-*}"
VALUE="${line##*-}"
if [ -z ${installedGems[$KEY]+x} ]; then
echo "key $KEY doesn't yet exist."
echo "append value $VALUE to key $KEY"
installedGems[$KEY]="$VALUE"
continue
else
echo "key already exists"
echo "append value $VALUE to $KEY if not already exists"
installedGems[$KEY]="${installedGems[$KEY]} $VALUE"
echo "result: ${installedGems[$KEY]}"
fi
done < <(find $directory -maxdepth 1 -type d -regextype posix-extended -regex "^${directory}\/[a-zA-Z0-9]+([-_]?[a-zA-Z0-9]+)*-[0-9]{1,3}(.[0-9]{1,3}){,3}\$")
}
parseGemVersions () {
local version_list
declare -Ag chosenGemVersions
while [[ "$current_token_index" -le "$ARGC" ]] && [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do
if versionOfGemInstalled? $gem $current_token; then
if [ -z ${chosenGemVersions[$gem]+x} ]; then
chosenGemVersions[$gem]=$current_token
# continue
else
chosenGemVersions[$gem]="${chosenGemVersions[$gem]} $current_token"
fi
else
parsing_error! "While parsing function $FUNCNAME, current version number $current_token is not installed!"
fi
echo "result: ${chosenGemVersions[$gem]}"
eat!
done
}
parse_gems_with_versions () {
# option --gems
# --gems gem-name-1 1.1.1 1.2.1 1.2.5 gem-name-2 latest gem-name-3 ...
unset gem
unset chosenGemVersions
gem=""
declare -Ag chosenGemVersions
while [ $current_token_index -le $ARGC ] && [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; do
if isWord? $current_token && [ ! "$current_token" = "latest" ]; then
if isWord? $current_token; then
if gemInstalled? $current_token; then
# We can conjecture that the word token is in fact a gems' name
gem=$current_token
local version_list
if isWord? $current_token && [ "$current_token" = "latest" ]; then
version_list=(${installedGems[$gem]})
chosenGemVersions[$gem]="${version_list[${#version_list} -1]}"
else
# gets list chosenGemVersions
parseGemVersions
fi
else
parsing_error! "Gem $token_name not installed!"
fi
fi
else
parsing_error! "While parsing function $FUNCNAME, "latest" is not a gemname!"
fi
eat!
done
}
答案1
我相信你的问题主要是误解 shell 脚本中的布尔类型。
您基本上可以用以下函数替换第一个辅助函数:
#!/bin/bash
set -o nounset
set -o errexit
is_word ()
{
local pattern='^[a-zA-Z0-9_-]+$'
[[ "$1" =~ $pattern ]]
}
is_version_number ()
{
local pattern='^[0-9]{1,2}(\.[0-9]{,2})*$'
[[ "$1" =~ $pattern ]]
}
# testing is_word ()
if is_word 'bla01'; then
echo IS word
else
echo NOT word
fi
# testing is_version_number ()
if is_version_number '1.1'; then
echo IS version number
else
echo NOT version number
fi
你看,不要尝试回显真/假或任何东西,测试命令([..]
或[[..]]
)本身返回正确的布尔值(实际上是整数),永远不要使用你自己的真/假构造。
此外,始终使用 ShellCheck (https://www.shellcheck.net/)来保护您的脚本,或调试它们。
好习惯也是使用set -o nounset
(set -u
)和set -o errexit
(set -e
)。
通过您问题的更新版本,我发现您可能已经习惯了 C 编程。shell 脚本中没有$ARGC
, 或。$ARGV
使用这些代替:
ARGC
(参数计数):"$#"
ARGV
(参数数组):"$@"
例如,您可以这样做:
eat_args ()
{
for arg in "$@"; do
current_char=${arg:0:1}
echo "$current_char"
done
}
eat_args "$@"
这对我来说没有多大意义,因为它打印每个给定参数的第一个字母。干杯,祝你好运。
答案2
解决方案是对通过运算符连接的条件进行分组||
。
while [[ $current_token_index -le $ARGC ]] && { [[ "$current_token" =~ ^[a-zA-Z0-9_-]+$ ]] || ! [[ "$current_token" =~ ^[0-9]{1,2}(\.[0-9]{,2})*$ ]]; }; do