从Shell脚本文件中提取版本号

48

我正在尝试编写一段bash脚本,用于递增给定的版本号。

{major}.{minor}.{revision}
例如。
1.2.13

有没有一种好的方法可以使用sed或awk轻松提取这三个数字,以便我可以增加{revision}号并输出完整的版本号字符串。

9个回答

76
$ v=1.2.13
$ echo "${v%.*}.$((${v##*.}+1))"
1.2.14

$ v=11.1.2.3.0
$ echo "${v%.*}.$((${v##*.}+1))"
11.1.2.3.1

这是它的工作原理:

字符串被分成两部分。

  • 第一部分包含除最后一个点和下一个字符以外的所有内容:${v%.*}
  • 第二部分包含除最后一个点及其之前的所有字符以外的所有内容:${v##*.}

第一部分按原样打印,后跟一个普通点和通过shell算术扩展增加1的最后一部分:$((x+1))


非常好的答案,适用于Linux、MacOS甚至Busybox/Alpine发行版。谢谢! - blagerweij

52

使用纯 Bash 数组的方法:

version='1.2.33'
a=( ${version//./ } )                   # replace points, split into array
((a[2]++))                              # increment revision (or other part)
version="${a[0]}.${a[1]}.${a[2]}"       # compose new version

2
我喜欢这个版本,因为它不会假设版本有多少部分。要组合新版本,需要设置 IFS 并使用 "${a[*]}"。谢谢,我能够很好地利用它! - Florian

49

我更喜欢使用“cut”命令来处理这种情况。

major=`echo $version | cut -d. -f1`
minor=`echo $version | cut -d. -f2`
revision=`echo $version | cut -d. -f3`
revision=`expr $revision + 1`

echo "$major.$minor.$revision"

我知道这不是最简单的方法,但对我而言,这是最容易理解和阅读的方法...


2
4个子shell,3个管道,至少3个完整的进程,仅仅为了改变一个或两个字符!这有点过分了。 - Fritz G. Mehner
6
@fgm 但至少它可以在使用bash或其他shell时正常工作。+1 当然,你也可以简化这个命令:echo "$(echo $version | cut -d. -f-2).$(expr $(echo $version | cut -d. -f3) + 1)"。虽然它仍然包含了很多子shell和管道操作,但除非你在使用超级微型低功耗嵌入式设备,否则这应该不会成为问题。如果你追求速度的话,那么bash可能不是最佳选择;-) - Huygens

15

另一种Shell方式(展示了总是有多种方法来搞定这些东西的事实...):

$ echo 1.2.3 | ( IFS=".$IFS" ; read a b c && echo $a.$b.$((c + 1)) )
1.2.4

所以,我们可以这样做:

$ x=1.2.3
$ y=`echo $x | ( IFS=".$IFS" ; read a b c && echo $a.$b.$((c + 1)) )`
$ echo $y
1.2.4

+1,非常易懂的方法。您能解释一下为什么需要编写IFS=".$IFS"而不是简单地编写IFS="."吗?我已经检查了IFS=".",但它不能正常工作。 - bbaja42
有点多虑,如果默认值不在那里,某些东西将无法正常工作 :-) 我猜如果IFS不包括换行符(默认情况下它有一个空格、一个制表符和一个换行符),那么read函数就不能正常工作。话虽如此,我在这里刚刚尝试了一下,使用IFS="."也可以正常工作...(在cygwin下运行的bash 3.2.51)。 - Chris J

9

Awk使这变得非常简单:

echo "1.2.14" | awk -F \. {'print $1,$2, $3'}将打印出1 2 14。

标志 -F 指定分隔符。

如果您想保存其中一个值:

firstVariable=$(echo "1.2.14" | awk -F \. {'print $1'})


但是你需要为每个重复三次的 echo | awk 重复执行。 - David W.
真的。我更喜欢Cris J的答案。 - bbaja42
谢谢!如果有人只需要major.minor,可以使用以下命令获取:echo "1.2.14" | awk -F \. '{version=$1"."$2; print version}'将会打印出1.2 - Antônio Medeiros

7

我使用shell自带的单词分割功能,类似于

oIFS="$IFS"
IFS=.
set -- $version
IFS="$oIFS"

尽管由于字母或日期后缀以及其他令人烦恼的不一致部分,您需要注意版本号的问题。完成此操作后,位置参数将设置为$version的组件:

$1 = 1
$2 = 2
$3 = 13

($IFS 是一组单个字符,而不是字符串,所以这不能用于多字符字段分隔符,尽管您可以使用 IFS=.- 来在 .- 上进行拆分。)


我不确定这与我所问的问题有任何关系,也许我误解了$IFS是什么以及$version被设置为什么。 - Dougnukem
$IFS是shell用于执行自身字段(“单词”)拆分的内容;我正在保存原始值(空格、制表符、换行符),并将其设置为“.”,然后使用set强制$version进行单词拆分。我将在答案中详细说明。 - geekosaur
mod +1:非常巧妙。IFS是shell使用的“输入字段分隔符”。它通常设置为制表符、空格和换行符。geekasaur将其更改为句点。set -- $version用$version字段替换命令行参数,该字段由句点拆分。因此,这三个部分现在是$1、$2、$3。如果您不想使用set,可以尝试使用以下命令:echo $version | read major minor revision。它不够紧凑,但不会影响您仍然可能使用的命令行参数。 - David W.
@David:请注意,while循环可能在子shell中运行,可能会产生令人惊讶的结果(例如,循环中的变量设置不会被脚本的其余部分看到)。http://stackoverflow.com/questions/6245246/translating-bin-ksh-date-conversion-function-into-bin-sh - geekosaur
哇,你好快啊。我在我的注释中看到了 while 行并将其删除了。这不用一分钟就完成了。这是将输入导入 while read 循环的老习惯了。实际上,我放置的内容在 BASH 中不起作用,但在我使用的 Kornshell 中可以正常工作。它与 BASH 完全兼容,除非它不能正常工作。为了使其在 bash 中正常工作,您必须将 $version 作为此处文档放置。 - David W.
“geekosaur”所写的正是医生要求的!我测试了David W.提出的使用echo $version | read major minor revision而不是set -- $version的想法,但那根本没有起作用。 - smithfarm

2

受到jlliagre的回答启发,我制作了自己的版本,支持仅提供主要版本号的版本号。jlliagre的版本会将1 -> 1.2而非2。

这个版本适用于两种风格的版本号:

function increment_version()
    local VERSION="$1"

    local INCREMENTED_VERSION=
    if [[ "$VERSION" =~ .*\..* ]]; then
        INCREMENTED_VERSION="${VERSION%.*}.$((${VERSION##*.}+1))"
    else
        INCREMENTED_VERSION="$((${VERSION##*.}+1))"
    fi

    echo "$INCREMENTED_VERSION"
}

这将产生以下输出:
increment_version 1         -> 2 
increment_version 1.2       -> 1.3    
increment_version 1.2.9     -> 1.2.10 
increment_version 1.2.9.101 -> 1.2.9.102

1

我很惊讶没有人建议使用grep

以下是如何从文件名中获取完整版本(不限于x.y.z的长度)的方法:

filename="openshift-install-linux-4.12.0-ec.3.tar.gz"
find -name "$filename" | grep -Eo '([0-9]+)(\.?[0-9]+)*' | head -1
# 4.12.0

0

对 fgm 的解决方案进行小变化,使用内置的 read 命令将字符串拆分为数组。请注意,IFS 变量的作用域仅限于 read 命令(因此无需存储和恢复当前的 IFS 变量)。

version='1.2.33'
IFS='.' read -r -a a <<<"$version"
((a[2]++))
printf '%s\n' "${a[@]}" | nl
version="${a[0]}.${a[1]}.${a[2]}"
echo "$version"

请看:

参见:如何在Bash中按分隔符拆分字符串?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接