bash 如何在shell脚本中增加版本号?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8653126/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 21:24:50  来源:igfitidea点击:

How to increment version number in a shell script?

bashwine

提问by nnn

The following simple version control script is meant to find the last version number of a given file, increment it, run a given command with the newly created file (e.g., editor), and after that save it to stable. Since it's simple, it doesn't check anything since the script would be modified as needed. For instance, if the result won't be stable, the user can omit the last argument.

以下简单的版本控制脚本旨在查找给定文件的最后一个版本号,增加它,使用新创建的文件(例如,编辑器)运行给定命令,然后将其保存到稳定版。由于它很简单,它不会检查任何内容,因为脚本会根据需要进行修改。例如,如果结果不稳定,用户可以省略最后一个参数。

However, one major concern of the current functionality is how to implement the following: if the last section after dot has two digits, inc untill 99; if only 1, then inc until 9, then move to the previous section. The versions may have any positive integer number of sections.

但是,当前功能的一个主要关注点是如何实现以下内容:如果点之后的最后一部分有两位数字,则 inc 直到 99;如果只有 1,则 inc 直到 9,然后移至上一节。版本可以有任何正整数的部分。

1.2.3.44 -> 1.2.3.45
1.2.3.9 -> 1.2.4.0
1.2.3 -> 1.2.4
9 -> 10

The remaining issue is that it doesn't wait for a tabbed wine editor to close the file; the goal is to detect when the tab is closed. Also, could you explain how best to make sure that my variable names don't overwrite existing ones?

剩下的问题是它不会等待选项卡式葡萄酒编辑器关闭文件;目标是检测选项卡何时关闭。另外,你能解释一下如何最好地确保我的变量名不会覆盖现有的吗?

You can also offer other improvements.

您还可以提供其他改进。

#!/bin/bash
#Tested on bash 4.1.5
#All arguments in order: "folder with file" "file pattern" cmd [stable name]
folder=""
file_pattern=""
cmd=""
stable=""

cd "$folder"
last_version=$(ls --format=single-column --almost-all | \
    grep "$file_pattern" | \
    sed -nr 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*//p' | \
    sort -Vu | \
    tail -n 1)
last_version_file=$(ls --format=single-column --almost-all | \
    grep "$file_pattern" | \
    grep $last_version | \
    tail -n 1) #tail -n 1 is only needed to get 1 line if there are backup files with the same version number
new_version=$(echo $last_version | \
    gawk -F"." '{$NF+=1}{print 
#!/bin/bash
inc()
{
shopt -s extglob
    num=${last_version//./}
    let num++

    re=${last_version//./)(}
    re=${re//[0-9]/.}')'
    re=${re#*)}

    count=${last_version//[0-9]/}
    count=$(wc -c<<<$count)
    out=''
    for ((i=count-1;i>0;i--)) ; do
        out='.\'$i$out
    done

    sed -r s/$re$/$out/ <<<$num
}

folder=""
file_pattern=""
cmd=""
stable=""

cd "$folder"
last_version=$(ls --format=single-column --almost-all | \
    grep "$file_pattern" | \
    sed -nr 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*//p' | \
    sort -Vu | \
    tail -n 1) #--almost-all do not list implied . and ..
last_version_file=$(ls --format=single-column --almost-all | \
    grep "$file_pattern" | \
    grep $last_version | \
    tail -n 1) #tail -n 1 is only needed to get 1 line if there are backup files with the same version number
new_version=$(inc)
new_version_file=$(echo "$last_version_file" | \
    sed -r "s/$last_version/$new_version/")
cp "$last_version_file" "$new_version_file"
"$cmd" "$new_version_file" && \
    wait #works with gedit but not tabbed wine editor
[[ "$stable" ]] && \
    cp "$new_version_file" "$stable" #True if the length of string is non-zero.
RT}' OFS="." ORS="") #increments last section indefinitely new_version_file=$(echo "$last_version_file" | \ sed -r "s/$last_version/$new_version/") cp "$last_version_file" "$new_version_file" "$cmd" "$new_version_file" & \ wait #works with gedit but not with wine tabbed editor [[ "$stable" ]] && \ cp "$new_version_file" "$stable" #True if the length of string is non-zero.

Update: The following works on my pc, I will update it if improvements or solutions to unsolved problems are found:

更新:以下适用于我的电脑,如果发现未解决问题的改进或解决方案,我会更新它:

$ echo 1.2.3.4 | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}'
1.2.3.5

I appreciate the variety of solutions that have been offered, for they help with gaining a perspective and drawing a comparison.

我很欣赏所提供的各种解决方案,因为它们有助于获得视角和进行比较。

回答by kev

1.2.3.9  => 1.2.4.0
1.2.3.44 => 1.2.3.45
1.2.3.99 => 1.2.4.00
1.2.3.999=> 1.2.4.000
1.2.9    => 1.3.0
999      => 1000


#!/usr/bin/gawk -f

BEGIN{
    v[1] = "1.2.3.4"
    v[2] = "1.2.3.44"
    v[3] = "1.2.3.99"
    v[4] = "1.2.3"
    v[5] = "9"
    v[6] = "9.9.9.9"
    v[7] = "99.99.99.99"
    v[8] = "99.0.99.99"
    v[9] = ""

    for(i in v)
        printf("#%d: %s => %s\n", i, v[i], inc(v[i])) | "sort | column -t"
}

function inc(s,    a, len1, len2, len3, head, tail)
{
    split(s, a, ".")

    len1 = length(a)
    if(len1==0)
        return -1
    else if(len1==1)
        return s+1

    len2 = length(a[len1])
    len3 = length(a[len1]+1)

    head = join(a, 1, len1-1)
    tail = sprintf("%0*d", len2, (a[len1]+1)%(10^len2))

    if(len2==len3)
        return head "." tail
    else
        return inc(head) "." tail
}

function join(a, x, y,    s)
{
    for(i=x; i<y; i++)
        s = s a[i] "."
    return s a[y]
}

UPDATE:

更新:

$ chmod +x inc.awk
$ ./inc.awk
#1:  1.2.3.4      =>  1.2.3.5
#2:  1.2.3.44     =>  1.2.3.45
#3:  1.2.3.99     =>  1.2.4.00
#4:  1.2.3        =>  1.2.4
#5:  9            =>  10
#6:  9.9.9.9      =>  10.0.0.0
#7:  99.99.99.99  =>  100.00.00.00
#8:  99.0.99.99   =>  99.1.00.00
#9:  =>           -1


# Usage: increment_version <version> [<position>]
increment_version() {
 local v=
 if [ -z  ]; then 
    local rgx='^((?:[0-9]+\.)*)([0-9]+)($)'
 else 
    local rgx='^((?:[0-9]+\.){'$((-1))'})([0-9]+)(\.|$)'
    for (( p=`grep -o "\."<<<".$v"|wc -l`; p<; p++)); do 
       v+=.0; done; fi
 val=`echo -e "$v" | perl -pe 's/^.*'$rgx'.*$//'`
 echo "$v" | perl -pe s/$rgx.*$'/'`printf %0${#val}s $(($val+1))`/
}

# EXAMPLE   ------------->   # RESULT
increment_version 1          # 2
increment_version 1.0.0      # 1.0.1
increment_version 1 2        # 1.1
increment_version 1.1.1 2    # 1.2
increment_version 00.00.001  # 00.00.002

回答by Stephen M. Harris

Here are a couple more flexible options. Both accept a second argument to indicate which position to increment.

这里有几个更灵活的选项。两者都接受第二个参数来指示要增加的位置。

1. Simple function

1. 功能简单

For more predictable input.

对于更可预测的输入。

# Accepts a version string and prints it incremented by one.
# Usage: increment_version <version> [<position>] [<leftmost>]
increment_version() {
   local usage=" USAGE: $FUNCNAME [-l] [-t] <version> [<position>] [<leftmost>]
           -l : remove leading zeros
           -t : drop trailing zeros
    <version> : The version string.
   <position> : Optional. The position (starting with one) of the number 
                within <version> to increment.  If the position does not 
                exist, it will be created.  Defaults to last position.
   <leftmost> : The leftmost position that can be incremented.  If does not
                exist, position will be created.  This right-padding will
                occur even to right of <position>, unless passed the -t flag."

   # Get flags.
   local flag_remove_leading_zeros=0
   local flag_drop_trailing_zeros=0
   while [ "${1:0:1}" == "-" ]; do
      if [ "" == "--" ]; then shift; break
      elif [ "" == "-l" ]; then flag_remove_leading_zeros=1
      elif [ "" == "-t" ]; then flag_drop_trailing_zeros=1
      else echo -e "Invalid flag: \n$usage"; return 1; fi
      shift; done

   # Get arguments.
   if [ ${#@} -lt 1 ]; then echo "$usage"; return 1; fi
   local v=""             # version string
   local targetPos=${2-last}  # target position
   local minPos=${3-${2-0}}   # minimum position

   # Split version string into array using its periods. 
   local IFSbak; IFSbak=IFS; IFS='.' # IFS restored at end of func to                     
   read -ra v <<< "$v"               #  avoid breaking other scripts.

   # Determine target position.
   if [ "${targetPos}" == "last" ]; then 
      if [ "${minPos}" == "last" ]; then minPos=0; fi
      targetPos=$((${#v[@]}>${minPos}?${#v[@]}:$minPos)); fi
   if [[ ! ${targetPos} -gt 0 ]]; then
      echo -e "Invalid position: '$targetPos'\n$usage"; return 1; fi
   (( targetPos--  )) || true # offset to match array index

   # Make sure minPosition exists.
   while [ ${#v[@]} -lt ${minPos} ]; do v+=("0"); done;

   # Increment target position.
   v[$targetPos]=`printf %0${#v[$targetPos]}d $((10#${v[$targetPos]}+1))`;

   # Remove leading zeros, if -l flag passed.
   if [ $flag_remove_leading_zeros == 1 ]; then
      for (( pos=0; $pos<${#v[@]}; pos++ )); do
         v[$pos]=$((${v[$pos]}*1)); done; fi

   # If targetPosition was not at end of array, reset following positions to
   #   zero (or remove them if -t flag was passed).
   if [[ ${flag_drop_trailing_zeros} -eq "1" ]]; then
        for (( p=$((${#v[@]}-1)); $p>$targetPos; p-- )); do unset v[$p]; done
   else for (( p=$((${#v[@]}-1)); $p>$targetPos; p-- )); do v[$p]=0; done; fi

   echo "${v[*]}"
   IFS=IFSbak
   return 0
}

# EXAMPLE   ------------->   # RESULT
increment_version 1          # 2
increment_version 1 2        # 1.1
increment_version 1 3        # 1.0.1
increment_version 1.0.0      # 1.0.1
increment_version 1.2.3.9    # 1.2.3.10
increment_version 00.00.001  # 00.00.002
increment_version -l 00.001  # 0.2
increment_version 1.1.1.1 2   # 1.2.0.0
increment_version -t 1.1.1 2  # 1.2
increment_version v1.1.3      # v1.1.4
increment_version 1.2.9 2 4     # 1.3.0.0
increment_version -t 1.2.9 2 4  # 1.3
increment_version 1.2.9 last 4  # 1.2.9.1

2. Robust function

2. 功能强大

For use with scripts, or more customizability to apply to various versioning systems. It could use a couple more options, but as it stands now it works for my projects using the "major.minor[.maintenance[.build]]" version sequences.

用于脚本,或更多可定制性以应用于各种版本控制系统。它可以使用更多选项,但就目前而言,它适用于我使用“major.minor[.maintenance[.build]]”版本序列的项目。

$ cat versions
1.2.3.44
1.2.3.9
1.2.3
9
42.2-includes-postfix

$ perl -pe 's/^((\d+\.)*)(\d+)(.*)$/.(+1)./e' < versions
1.2.3.45
1.2.3.10
1.2.4
10
42.3-includes-postfix

Obviously, this is excessive just to increment a version string. But I wrote this because I had a need for different types of projects, and because if speed is not an issue, I prefer reusability over tweaking the same code across dozens of scripts. I guess that's just my object-oriented side leaking into my scripts.

显然,这只是增加版本字符串是多余的。但我写这个是因为我需要不同类型的项目,而且如果速度不是问题,我更喜欢可重用性而不是在几十个脚本中调整相同的代码。我想这只是我的面向对象的一面泄漏到我的脚本中。

回答by Pyrolistical

Here is an even shorter version that also supports a postfix (nice for -SNAPSHOT)

这是一个更短的版本,它也支持后缀(适用于 -SNAPSHOT)

increment_version ()
{
  declare -a part=( ${1//\./ } )
  declare    new
  declare -i carry=1

  for (( CNTR=${#part[@]}-1; CNTR>=0; CNTR-=1 )); do
    len=${#part[CNTR]}
    new=$((part[CNTR]+carry))
    [ ${#new} -gt $len ] && carry=1 || carry=0
    [ $CNTR -gt 0 ] && part[CNTR]=${new: -len} || part[CNTR]=${new}
  done
  new="${part[*]}"
  echo -e "${new// /.}"
} 

version='1.2.3.44'

increment_version $version

Explanation

解释

I used regex to capture 3 parts. The stuff before the last position, the number to increment, and the stuff after.

我使用正则表达式来捕获 3 个部分。最后一个位置之前的东西,要增加的数字,以及之后的东西。

  • ((\d+\.)*)- stuff of the from 1.1.1.1.1.
  • (\d+)- the last digit
  • (.*)- the stuff after the last digit
  • ((\d+\.)*)- 来自 1.1.1.1.1 的东西。
  • (\d+)- 最后一位
  • (.*)- 最后一位数字之后的内容

I then use the e option to allow expressions in the replace part. Note with the e option \1 becomes a variable $1 and you need to concatenate variables with the dot operator.

然后我使用 e 选项来允许替换部分中的表达式。注意 e 选项 \1 变成变量 $1 并且您需要使用点运算符连接变量。

  • $1- the capture group of 1.1.1.1.1.
  • ($3+1)- increment the last digit. note $2 is used in the sub group of $1 to get the repeated 1.
  • $4- the stuff after the last digit
  • $1- 1.1.1.1.1 的捕获组。
  • ($3+1)- 增加最后一位。注意 $2 用于 $1 的子组中以获取重复的 1。
  • $4- 最后一位数字之后的内容

回答by Fritz G. Mehner

Pure Bash:

纯重击:

1.2.3.45

result:

结果:

$ cat versions
1.2.3.44
1.2.3.9
1.2.3
9

$ cat versions | perl -ne 'chomp; print join(".", splice(@{[split/\./,$_]}, 0, -1), map {++$_} pop @{[split/\./,$_]}), "\n";'
1.2.3.45
1.2.3.10
1.2.4
10

The version string is split and stored in the array part. The loop goes from the last to the first part of the version. The last part will be incremented and possibly cut down to its original length. A carry is taken to the next part.

版本字符串被拆分并存储在数组部分中。循环从版本的最后部分到第一部分。最后一部分将增加并可能减少到其原始长度。进位被带到下一部分。

回答by sorpigal

Tired of bash? Why not try Perl?

厌倦了 bash?为什么不试试 Perl?

function increase_version() {
    python - "" <<EOF
import sys
version = sys.argv[1]
base, _, minor = version.rpartition('.')
print(base + '.' + str(int(minor) + 1))
EOF
}

Not quite in compliance with the requirement, of course.

当然不完全符合要求。

回答by kokosing

Another option is to use Python. I think this way it a bit more readable than using plain Bash or Perl.

另一种选择是使用 Python。我认为这种方式比使用普通的 Bash 或 Perl 更具可读性。

if  [ $(expr length $rNum) = "2" ] ; then 
    if [ $rNum -lt 99 ]; then 
        rNum=$(($rNum + 1))
    else rNum=0
         dNum=$(($dNum + 1)) #some additional logic for $dNum > 9 also needed
    fi
elif [ $(expr length $dNum) = "1" ] ; then
    ...
    ...
fi

回答by venzen

Determining a version number for a software project is based on its relative change / functionality / development stage / revision. Consequent increments to the version and revision numbering is ideally a process that should be done by a human. However, not to second-guess your motivation for writing this script, here is my suggestion.

确定软件项目的版本号是基于其相对变化/功能/开发阶段/修订。理想情况下,版本和修订编号的后续增量应该由人工完成。但是,不要怀疑您编写此脚本的动机,这是我的建议。

Include some logic in your script that will do exactly what you describe in your requirement

在您的脚本中包含一些逻辑,这些逻辑将完全符合您在要求中的描述

"...if the last section after dot has two digits, inc until 99; if only 1, then inc until 9 ... "

“...如果点后面的最后一部分有两位数,则直到 99 为止;如果只有 1,则直到 9 为止......”

Assuming the third position is the development stage number $dNumand the fourth (last) position is the revision number $rNum:

假设第三个位置是开发阶段号$dNum,第四个(最后)位置是修订号$rNum

new_version="$majNum.minNum.$dNum.$rNum"
new_version_file="$old_file.$new_version"

Perhaps a function will allow the most succinct way of handling all positions (majNum.minNum.dNum.rNum).

也许函数将允许以最简洁的方式处理所有位置 (majNum.minNum.dNum.rNum)。

You will have to separate the project name and version number components of your filename in your script and then construct the version number with all its positions, and finally reconstruct the filename with something like

您必须在脚本中将文件名的项目名称和版本号部分分开,然后用它的所有位置构造版本号,最后用类似的东西重建文件名

#! /bin/bash
for v in 1.2.3.44 1.2.3.9 1.2.3 9 1.4.29.9 9.99.9 ; do
    echo -n $v '-> '

    num=${v//./}
    let num++

    re=${v//./)(}
    re=${re//[0-9]/.}')'
    re=${re#*)}

    count=${v//[0-9]/}
    count=$(wc -c<<<$count)
    out=''
    for ((i=count-1;i>0;i--)) ; do
        out='.\'$i$out
    done

    sed -r s/$re$/$out/ <<<$num
done

Hope that helps and check this SO discussionas well as this wikipedia entryif you want to know more about versioning conventions.

如果您想了解有关版本控制约定的更多信息,希望可以帮助并检查此 SO 讨论以及此 wikipedia 条目

回答by choroba

Using just bash, wc and sed:

只使用 bash、wc 和 sed:

NEXTVERSION=$(echo ${VERSION} | awk -F. -v OFS=. '{$NF++;print}')

回答by Chris Jones

For just incrementing the dewey decimal version: awk -F. -v OFS=. '{$NF++;print}'

仅增加杜威十进制版本: awk -F. -v OFS=. '{$NF++;print}'

Or in a shell script:

或者在 shell 脚本中:

##代码##