Bash:从给定范围获取最新的 semver - 同时减少冗长的条件逻辑
Bash: Obtain latest semver from a given range - whilst reducing verbose conditional logic
背景
我正在开发一个 bash 脚本来从 semver 个版本的数组中找到给定范围内的最新版本。
(example.sh) 下面的 code/gist 包含一个名为 log_latest_version_in_range
的函数,它接受一个参数。参数指定包名称和 semver range。以下是调用该函数的几个示例:
# passing one comparator.
log_latest_version_in_range "quxx@\<=1.0.1"
# passing two comparators.
log_latest_version_in_range "quxx@\>=0.1.0\ \<0.2.6"
调用该函数类似于您可能如何为特定 semver 范围内的包 运行 npm install。传递给函数的参数包括:
- 包名(例如
quux
)后跟 @
符号
- 一个或两个 比较器 指定:
- 运算符(即
<
、<=
、>
或 >=
)
- 一个版本(例如
0.2.6
)
注;该参数包括 \
用于转义目的以避免分词。但这有点无关紧要,它们将存在于参数中并且无法更改。
当前形式的 example.sh 成功地 produce/log 了预期的结果。
问题
为了创建 versions_in_range
数组,程序在 VERSIONS
数组上循环,并执行大量条件分支以确定版本是否在范围内 (从行:43)。如果可能的话,我想重构它。在 bash?
中是否有更简洁的方法来实现这一点?
example.sh
#!/usr/bin/env bash
declare -ra VERSIONS=(\
0.1.0 \
0.1.1 \
0.1.2 \
0.2.0 \
0.2.1 \
0.2.2 \
0.2.3 \
0.2.4 \
0.2.5 \
0.2.6 \
0.2.7 \
0.2.8 \
0.2.9 \
0.2.10 \
0.2.11 \
0.2.12 \
0.2.13 \
0.2.14 \
0.2.15)
log_latest_version_in_range () {
local pkg_name comparator_count
pkg_name=$(sed "s/@.*//" <<< "")
comparator_count=$(awk -F " " '{ print NF }' <<< "")
if [[ comparator_count -gt 2 ]]; then
printf "%b" "Cannot specify greater than two comparators\n"
return
fi
# Get operator and version for each comparator in comparator set.
local opr1 ver1 opr2 ver2
opr1=$( sed "s/.*@//; s/\\//g; s/ .*//; s/[^>=<].*//" <<< "")
ver1=$( sed "s/.*@//; s/\\//g; s/ .*//; s/.*[>=<]//" <<< "")
opr2=$( sed "s/.*@//; s/\\//g; s/.* //; s/[^>=<].*//" <<< "")
ver2=$( sed "s/.*@//; s/\\//g; s/.* //; s/.*[>=<]//" <<< "")
# Create an array of all versions that are within range.
local -a versions_in_range
for v in "${VERSIONS[@]}"; do
if [ "$comparator_count" == "1" ]; then
if [ "$opr1" == ">=" ] && is_ge "$v" "$ver1"; then
versions_in_range+=("$v")
elif [ "$opr1" == ">" ] && is_gt "$v" "$ver1"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<=" ] && is_le "$v" "$ver1"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<" ] && is_lt "$v" "$ver1"; then
versions_in_range+=("$v")
fi
elif [ "$comparator_count" == "2" ]; then
if [ "$opr1" == ">=" ] && [ "$opr2" == "<" ]\
&& is_ge "$v" "$ver1" && is_lt "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == ">=" ] && [ "$opr2" == "<=" ]\
&& is_ge "$v" "$ver1" && is_le "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<" ] && [ "$opr2" == ">=" ]\
&& is_lt "$v" "$ver1" && is_ge "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<" ] && [ "$opr2" == ">" ]\
&& is_lt "$v" "$ver1" && is_gt "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<=" ] && [ "$opr2" == ">" ]\
&& is_le "$v" "$ver1" && is_gt "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<=" ] && [ "$opr2" == ">=" ]\
&& is_le "$v" "$ver1" && is_ge "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == ">" ] && [ "$opr2" == "<" ]\
&& is_gt "$v" "$ver1" && is_lt "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == ">" ] && [ "$opr2" == "<=" ]\
&& is_gt "$v" "$ver1" && is_le "$v" "$ver2"; then
versions_in_range+=("$v")
fi
fi
done
# npm registry may not have version available within the range.
if [ "${#versions_in_range[@]}" == 0 ]; then
local mssg="Version cannot be found within range\n"
printf "%b" "$mssg"
return 0
fi
# Get the last/latest version in range
local latest_version="${versions_in_range[${#versions_in_range[@]}-1]}"
printf "%b" "$latest_version\n"
}
is_lt () {
test "$(echo "$@" | tr " " "\n" | sort -t. -k1,1nr -k2,2nr -k3,3nr -k4,4nr\
| head -n 1)" != ""
}
is_gt () {
test "$(echo "$@" | tr " " "\n" | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n\
| head -n 1)" != ""
}
is_le () {
test "$(echo "$@" | tr " " "\n" | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n\
| head -n 1)" == ""
}
is_ge () {
test "$(echo "$@" | tr " " "\n" | sort -t. -k1,1nr -k2,2nr -k3,3nr -k4,4nr\
| head -n 1)" == ""
}
#------------------------------------------------------------------------------
# TESTING
#------------------------------------------------------------------------------
# Passing two comparators
printf "%b" "======= Two Comparators args ========\n"
log_latest_version_in_range "quxx@\>=0.1.0\ \<0.2.6" # --> 0.2.5
log_latest_version_in_range "quxx@\>=0.2.8\ \<=0.2.13" # --> 0.2.13
log_latest_version_in_range "quxx@\<0.2.11\ \>=0.2.8" # --> 0.2.10
log_latest_version_in_range "quxx@\<0.2.15\ \>0.2.8" # --> 0.2.14
log_latest_version_in_range "quxx@\<=0.2.15\ \>0.2.8" # --> 0.2.15
log_latest_version_in_range "quxx@\<=0.2.0\ \>=0.1.0" # --> 0.2.0
log_latest_version_in_range "quxx@\>0.2.0\ \<0.2.6" # --> 0.2.5
log_latest_version_in_range "quxx@\>0.2.0\ \<=0.2.6" # --> 0.2.6
printf "%b" "======== One Comparator arg =========\n"
log_latest_version_in_range "quxx@\>=0.2.10" # --> 0.2.15
log_latest_version_in_range "quxx@\>0.2.10" # --> 0.2.15
log_latest_version_in_range "quxx@\<=0.2.10" # --> 0.2.10
log_latest_version_in_range "quxx@\<0.2.10" # --> 0.2.9
printf "%b" "=============== Other ==============\n"
log_latest_version_in_range "quxxx@\>0.2.14\ \<0.2.15\ \<=1.0.0"
# --> Cannot specify greater than two comparators
log_latest_version_in_range "quxx@\>0.2.14\ \<0.2.15"
# --> Version cannot be found within range
来自运行宁example.sh的正确output/log如下:
======= Two Comparators args ========
0.2.5
0.2.13
0.2.10
0.2.14
0.2.15
0.2.0
0.2.5
0.2.6
======== One Comparator arg =========
0.2.15
0.2.15
0.2.10
0.2.9
=============== Other ==============
Cannot specify greater than two comparators
Version cannot be found within range
"I'd like to refactor this if possible. Is there a more terse way to achieve that in bash?"
我最终选择重构 versions_in_range
数组的值是如何通过使用具有多个条件的 if
语句来确定的。
总体而言,这减少了 elif ...; then
语句的使用,进而导致 versions_in_range+=("$v")
.
的实例减少
重构后的片段 (从第 43 行开始):
# Create an array of all versions that are within range.
local -a versions_in_range
for v in "${VERSIONS[@]}"; do
if [ "$comparator_count" == "1" ]; then
if ( [ "$opr1" == ">=" ] && is_ge "$v" "$ver1" ) ||
( [ "$opr1" == ">" ] && is_gt "$v" "$ver1" ) ||
( [ "$opr1" == "<=" ] && is_le "$v" "$ver1" ) ||
( [ "$opr1" == "<" ] && is_lt "$v" "$ver1" ); then
versions_in_range+=("$v")
fi
elif [ "$comparator_count" == "2" ]; then
if ( [ "$opr1" == ">=" ] && [ "$opr2" == "<" ] &&
is_ge "$v" "$ver1" && is_lt "$v" "$ver2" ) ||
( [ "$opr1" == ">=" ] && [ "$opr2" == "<=" ] &&
is_ge "$v" "$ver1" && is_le "$v" "$ver2" ) ||
( [ "$opr1" == "<" ] && [ "$opr2" == ">=" ] &&
is_lt "$v" "$ver1" && is_ge "$v" "$ver2" ) ||
( [ "$opr1" == "<" ] && [ "$opr2" == ">" ] &&
is_lt "$v" "$ver1" && is_gt "$v" "$ver2" ) ||
( [ "$opr1" == "<=" ] && [ "$opr2" == ">" ] &&
is_le "$v" "$ver1" && is_gt "$v" "$ver2" ) ||
( [ "$opr1" == "<=" ] && [ "$opr2" == ">=" ] &&
is_le "$v" "$ver1" && is_ge "$v" "$ver2" ) ||
( [ "$opr1" == ">" ] && [ "$opr2" == "<" ] &&
is_gt "$v" "$ver1" && is_lt "$v" "$ver2" ) ||
( [ "$opr1" == ">" ] && [ "$opr2" == "<=" ] &&
is_gt "$v" "$ver1" && is_le "$v" "$ver2" ); then
versions_in_range+=("$v")
fi
fi
done
背景
我正在开发一个 bash 脚本来从 semver 个版本的数组中找到给定范围内的最新版本。
(example.sh) 下面的 code/gist 包含一个名为 log_latest_version_in_range
的函数,它接受一个参数。参数指定包名称和 semver range。以下是调用该函数的几个示例:
# passing one comparator.
log_latest_version_in_range "quxx@\<=1.0.1"
# passing two comparators.
log_latest_version_in_range "quxx@\>=0.1.0\ \<0.2.6"
调用该函数类似于您可能如何为特定 semver 范围内的包 运行 npm install。传递给函数的参数包括:
- 包名(例如
quux
)后跟@
符号 - 一个或两个 比较器 指定:
- 运算符(即
<
、<=
、>
或>=
) - 一个版本(例如
0.2.6
)
- 运算符(即
注;该参数包括 \
用于转义目的以避免分词。但这有点无关紧要,它们将存在于参数中并且无法更改。
example.sh 成功地 produce/log 了预期的结果。
问题
为了创建 versions_in_range
数组,程序在 VERSIONS
数组上循环,并执行大量条件分支以确定版本是否在范围内 (从行:43)。如果可能的话,我想重构它。在 bash?
example.sh
#!/usr/bin/env bash
declare -ra VERSIONS=(\
0.1.0 \
0.1.1 \
0.1.2 \
0.2.0 \
0.2.1 \
0.2.2 \
0.2.3 \
0.2.4 \
0.2.5 \
0.2.6 \
0.2.7 \
0.2.8 \
0.2.9 \
0.2.10 \
0.2.11 \
0.2.12 \
0.2.13 \
0.2.14 \
0.2.15)
log_latest_version_in_range () {
local pkg_name comparator_count
pkg_name=$(sed "s/@.*//" <<< "")
comparator_count=$(awk -F " " '{ print NF }' <<< "")
if [[ comparator_count -gt 2 ]]; then
printf "%b" "Cannot specify greater than two comparators\n"
return
fi
# Get operator and version for each comparator in comparator set.
local opr1 ver1 opr2 ver2
opr1=$( sed "s/.*@//; s/\\//g; s/ .*//; s/[^>=<].*//" <<< "")
ver1=$( sed "s/.*@//; s/\\//g; s/ .*//; s/.*[>=<]//" <<< "")
opr2=$( sed "s/.*@//; s/\\//g; s/.* //; s/[^>=<].*//" <<< "")
ver2=$( sed "s/.*@//; s/\\//g; s/.* //; s/.*[>=<]//" <<< "")
# Create an array of all versions that are within range.
local -a versions_in_range
for v in "${VERSIONS[@]}"; do
if [ "$comparator_count" == "1" ]; then
if [ "$opr1" == ">=" ] && is_ge "$v" "$ver1"; then
versions_in_range+=("$v")
elif [ "$opr1" == ">" ] && is_gt "$v" "$ver1"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<=" ] && is_le "$v" "$ver1"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<" ] && is_lt "$v" "$ver1"; then
versions_in_range+=("$v")
fi
elif [ "$comparator_count" == "2" ]; then
if [ "$opr1" == ">=" ] && [ "$opr2" == "<" ]\
&& is_ge "$v" "$ver1" && is_lt "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == ">=" ] && [ "$opr2" == "<=" ]\
&& is_ge "$v" "$ver1" && is_le "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<" ] && [ "$opr2" == ">=" ]\
&& is_lt "$v" "$ver1" && is_ge "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<" ] && [ "$opr2" == ">" ]\
&& is_lt "$v" "$ver1" && is_gt "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<=" ] && [ "$opr2" == ">" ]\
&& is_le "$v" "$ver1" && is_gt "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == "<=" ] && [ "$opr2" == ">=" ]\
&& is_le "$v" "$ver1" && is_ge "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == ">" ] && [ "$opr2" == "<" ]\
&& is_gt "$v" "$ver1" && is_lt "$v" "$ver2"; then
versions_in_range+=("$v")
elif [ "$opr1" == ">" ] && [ "$opr2" == "<=" ]\
&& is_gt "$v" "$ver1" && is_le "$v" "$ver2"; then
versions_in_range+=("$v")
fi
fi
done
# npm registry may not have version available within the range.
if [ "${#versions_in_range[@]}" == 0 ]; then
local mssg="Version cannot be found within range\n"
printf "%b" "$mssg"
return 0
fi
# Get the last/latest version in range
local latest_version="${versions_in_range[${#versions_in_range[@]}-1]}"
printf "%b" "$latest_version\n"
}
is_lt () {
test "$(echo "$@" | tr " " "\n" | sort -t. -k1,1nr -k2,2nr -k3,3nr -k4,4nr\
| head -n 1)" != ""
}
is_gt () {
test "$(echo "$@" | tr " " "\n" | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n\
| head -n 1)" != ""
}
is_le () {
test "$(echo "$@" | tr " " "\n" | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n\
| head -n 1)" == ""
}
is_ge () {
test "$(echo "$@" | tr " " "\n" | sort -t. -k1,1nr -k2,2nr -k3,3nr -k4,4nr\
| head -n 1)" == ""
}
#------------------------------------------------------------------------------
# TESTING
#------------------------------------------------------------------------------
# Passing two comparators
printf "%b" "======= Two Comparators args ========\n"
log_latest_version_in_range "quxx@\>=0.1.0\ \<0.2.6" # --> 0.2.5
log_latest_version_in_range "quxx@\>=0.2.8\ \<=0.2.13" # --> 0.2.13
log_latest_version_in_range "quxx@\<0.2.11\ \>=0.2.8" # --> 0.2.10
log_latest_version_in_range "quxx@\<0.2.15\ \>0.2.8" # --> 0.2.14
log_latest_version_in_range "quxx@\<=0.2.15\ \>0.2.8" # --> 0.2.15
log_latest_version_in_range "quxx@\<=0.2.0\ \>=0.1.0" # --> 0.2.0
log_latest_version_in_range "quxx@\>0.2.0\ \<0.2.6" # --> 0.2.5
log_latest_version_in_range "quxx@\>0.2.0\ \<=0.2.6" # --> 0.2.6
printf "%b" "======== One Comparator arg =========\n"
log_latest_version_in_range "quxx@\>=0.2.10" # --> 0.2.15
log_latest_version_in_range "quxx@\>0.2.10" # --> 0.2.15
log_latest_version_in_range "quxx@\<=0.2.10" # --> 0.2.10
log_latest_version_in_range "quxx@\<0.2.10" # --> 0.2.9
printf "%b" "=============== Other ==============\n"
log_latest_version_in_range "quxxx@\>0.2.14\ \<0.2.15\ \<=1.0.0"
# --> Cannot specify greater than two comparators
log_latest_version_in_range "quxx@\>0.2.14\ \<0.2.15"
# --> Version cannot be found within range
来自运行宁example.sh的正确output/log如下:
======= Two Comparators args ========
0.2.5
0.2.13
0.2.10
0.2.14
0.2.15
0.2.0
0.2.5
0.2.6
======== One Comparator arg =========
0.2.15
0.2.15
0.2.10
0.2.9
=============== Other ==============
Cannot specify greater than two comparators
Version cannot be found within range
"I'd like to refactor this if possible. Is there a more terse way to achieve that in bash?"
我最终选择重构 versions_in_range
数组的值是如何通过使用具有多个条件的 if
语句来确定的。
总体而言,这减少了 elif ...; then
语句的使用,进而导致 versions_in_range+=("$v")
.
重构后的片段 (从第 43 行开始):
# Create an array of all versions that are within range.
local -a versions_in_range
for v in "${VERSIONS[@]}"; do
if [ "$comparator_count" == "1" ]; then
if ( [ "$opr1" == ">=" ] && is_ge "$v" "$ver1" ) ||
( [ "$opr1" == ">" ] && is_gt "$v" "$ver1" ) ||
( [ "$opr1" == "<=" ] && is_le "$v" "$ver1" ) ||
( [ "$opr1" == "<" ] && is_lt "$v" "$ver1" ); then
versions_in_range+=("$v")
fi
elif [ "$comparator_count" == "2" ]; then
if ( [ "$opr1" == ">=" ] && [ "$opr2" == "<" ] &&
is_ge "$v" "$ver1" && is_lt "$v" "$ver2" ) ||
( [ "$opr1" == ">=" ] && [ "$opr2" == "<=" ] &&
is_ge "$v" "$ver1" && is_le "$v" "$ver2" ) ||
( [ "$opr1" == "<" ] && [ "$opr2" == ">=" ] &&
is_lt "$v" "$ver1" && is_ge "$v" "$ver2" ) ||
( [ "$opr1" == "<" ] && [ "$opr2" == ">" ] &&
is_lt "$v" "$ver1" && is_gt "$v" "$ver2" ) ||
( [ "$opr1" == "<=" ] && [ "$opr2" == ">" ] &&
is_le "$v" "$ver1" && is_gt "$v" "$ver2" ) ||
( [ "$opr1" == "<=" ] && [ "$opr2" == ">=" ] &&
is_le "$v" "$ver1" && is_ge "$v" "$ver2" ) ||
( [ "$opr1" == ">" ] && [ "$opr2" == "<" ] &&
is_gt "$v" "$ver1" && is_lt "$v" "$ver2" ) ||
( [ "$opr1" == ">" ] && [ "$opr2" == "<=" ] &&
is_gt "$v" "$ver1" && is_le "$v" "$ver2" ); then
versions_in_range+=("$v")
fi
fi
done