【问题标题】:How to increment version number in a shell script?如何在 shell 脚本中增加版本号?
【发布时间】:2012-01-29 00:02:30
【问题描述】:

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

但是,当前功能的一个主要问题是如何实现以下功能:如果 dot 之后的最后一个部分有两位数,则 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

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

您还可以提供其他改进。

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

cd "$folder"
last_version=$(ls --format=single-column --almost-all | \
    grep "$file_pattern" | \
    sed -nr 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/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 $0RT}' 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.

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

#!/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="$1"
file_pattern="$2"
cmd="$3"
stable="$4"

cd "$folder"
last_version=$(ls --format=single-column --almost-all | \
    grep "$file_pattern" | \
    sed -nr 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/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.

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

【问题讨论】:

  • 我从未使用过 wine 选项卡式编辑器,因此无法提供帮助,但请详细说明“..确保我的变量名不会覆盖现有的变量名.. ' - 那是什么背景?脚本中有特殊的 shell 变量或 vars?
  • 9.9.9 怎么样?是9.10.0 还是10.0.0?
  • 我的意思是任何由 wine 运行的选项卡式编辑器,例如记事本选项卡。至于文件夹,我的意思是如果一个人自己使用 $folder,例如,对于他们的编辑器命令: alias edit="$folder\Notepad2" 那么它可能无法在这个脚本中正常工作。
  • 我认为 9.9.9 应该去 10.0.0。
  • 版本号通常不像小数,所以 9.9.9 通常应该增加到 9.9.10。此外,0.100 > 0.50 > 0.10 > 0.5 > 0.1。

标签: bash wine


【解决方案1】:
$ 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

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]
}

$ 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

【讨论】:

  • 哎呀!为什么我这些年来一直忽略awk?
  • 我尝试测试您的更新版本,但它挂起我的电脑并且从不打印任何内容。
  • @nnn 尝试从function inc(s, a, len1, len2, len3, head, tail) 中删除a,。你能发布错误信息吗?
  • @nnn 你的 awk 版本太旧了。
  • 想要使用标准增量的,9.9.9 =&gt; 9.9.10使用这个代码echo 9.9.9.9 | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF&gt;1{$NF=sprintf("%0*d", length($NF), ($NF+1)); print}'
【解决方案2】:

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

1。简单功能

更多可预测的输入。

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

2。强大的功能

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

# 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 [ "$1" == "--" ]; then shift; break
      elif [ "$1" == "-l" ]; then flag_remove_leading_zeros=1
      elif [ "$1" == "-t" ]; then flag_drop_trailing_zeros=1
      else echo -e "Invalid flag: ${1}\n$usage"; return 1; fi
      shift; done

   # Get arguments.
   if [ ${#@} -lt 1 ]; then echo "$usage"; return 1; fi
   local v="${1}"             # 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

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

【讨论】:

  • 这个答案的忠实粉丝,一件事是在第 49 行“增量目标位置”它在错误的基础上增加,所以当版本为 08 时它不能增加。修复很简单,改变以下行:v[$targetPos]=`printf %0${#v[$targetPos]}d $((10#${v[$targetPos]}+1))`;
【解决方案3】:

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

或者在 shell 脚本中:

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

【讨论】:

  • 对我来说,这是最好的解决方案,因为我不需要它完全健壮或可定制
  • 这一定是答案!
  • 合法。非常合法。
  • 耶,喜欢这个。
  • 这在 ubuntu 20.04 1.2.0 上不起作用 输出:1.2.0.,awk 版本更改或区域设置特定的东西?我不确定我用过别的东西..
【解决方案4】:

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

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

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

说明

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

  • ((\d+\.)*) - 来自 1.1.1.1.1 的东西。
  • (\d+) - 最后一位
  • (.*) - 最后一位数字之后的内容

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

  • $1 - 1.1.1.1.1 的捕获组。
  • ($3+1) - 增加最后一位数字。注意 $2 在 $1 的子组中用于得到重复的 1。
  • $4 - 最后一位数字之后的内容

【讨论】:

  • 注 1.2.3.9 变为 1.2.3.10,而不是问题要求的 1.2.4.0。我认为这是一个虚假的要求,在版本控制时没有人想要。
【解决方案5】:

1。只增加选中的部分

用法

increment_version 1.39.0 0 # 2.39.0
increment_version 1.39.0 1 # 1.40.0
increment_version 1.39.0 2 # 1.39.1

代码

### Increments the part of the string
## $1: version itself
## $2: number of part: 0 – major, 1 – minor, 2 – patch
increment_version() {
  local delimiter=.
  local array=($(echo "$1" | tr $delimiter '\n'))
  array[$2]=$((array[$2]+1))
  echo $(local IFS=$delimiter ; echo "${array[*]}")
}

@dimpiax答案的简化版


编辑:我创建了这个脚本的另一个版本,如果最重要的部分发生更改,则将零放在不太重要的部分上。请注意使用部分的不同预期结果。

2。增加所选部分并在后续部分上放置零

用法

increment_version 1.39.3 0 # 2.0.0
increment_version 1.39.3 1 # 1.40.0
increment_version 1.39.3 2 # 1.39.4
#!/bin/bash

### Increments the part of the string
## $1: version itself
## $2: number of part: 0 – major, 1 – minor, 2 – patch

increment_version() {
  local delimiter=.
  local array=($(echo "$1" | tr $delimiter '\n'))
  array[$2]=$((array[$2]+1))
  if [ $2 -lt 2 ]; then array[2]=0; fi
  if [ $2 -lt 1 ]; then array[1]=0; fi
  echo $(local IFS=$delimiter ; echo "${array[*]}")
}

【讨论】:

  • 哇,是最好的解决方案!
  • 嗨看起来是一个很好的解决方案,我正在尝试将输出输出到 var,但是,在终端中运行时它不会返回任何内容——我做错了什么:-/ @ 987654326@
  • @einonsy 如果你把函数单独放在 increment.sh 里面,你必须调用它。为简单起见,您可以像这样在同一脚本的最后一行调用: increment_version $1 $2 或者您可以删除该函数并运行它。为此,您必须删除所有出现的字符串“local”,因为它们只应该在函数内部。
【解决方案6】:

纯猛击:

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

结果:

1.2.3.45

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

【讨论】:

  • 在我的电脑上,它打印的是空格而不是句号,但除此之外它可以正常工作。 + 它有一个解释。
  • nnn评论中提到的问题已修复。
  • @fgm 如果我们使用 1.2.9.9 那么它显示 1.2.10.0 它应该是 1.3.0.0
  • @damithH 它对我来说可以正常工作。 Bash/shell 版本问题?
【解决方案7】:

对于常见的用例增加只是补丁版本,保持简洁:

$ awk -vFS=. -vOFS=. '{$NF++;print}' <<<1.2.99
1.2.100
Explanation
-vFS=. Set Field Separator to .
-vOFS=. Set Output Field Separator to .
{$NF++;print} Reference last field, increment its value
&lt;&lt;&lt;str Send str to awk's standard input

【讨论】:

    【解决方案8】:

    用法

    increment_version 1.39.0 0 # 2.39.0
    increment_version 1.39.0 1 # 1.40.0
    increment_version 1.39.0 2 # 1.39.1
    

    代码

    ### Increments the part of the string
    ## $1: version itself
    ## $2: number of part: 0 – major, 1 – minor, 2 – patch
    increment_version() {
      local delimiter=.
      local array=($(echo "$1" | tr $delimiter '\n'))
    
      for index in ${!array[@]}; do
        if [ $index -eq $2 ]; then
          local value=array[$index]
          value=$((value+1))
          array[$index]=$value
          break
        fi
      done
    
      echo $(IFS=$delimiter ; echo "${array[*]}")
    }
    

    【讨论】:

      【解决方案9】:

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

      在您的脚本中包含一些逻辑,这些逻辑将完全按照您在需求中描述的方式进行

      "...如果点后的最后一段有两位数,则递增到 99;如果只有 1,则递增到 9 ..."

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

      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
      

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

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

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

      希望对您有所帮助,如果您想了解有关版本控制约定的更多信息,请查看 this SO discussion 和 this wikipedia entry。

      【讨论】:

        【解决方案10】:

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

        $ 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
        

        当然不完全符合要求。

        【讨论】:

        • (也就是说:&lt;versions perl -ne ... 将让 perl 直接从文件中读取,而不是从附加到 cat 副本的 FIFO 中读取,这只会增加开销)。跨度>
        【解决方案11】:

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

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

        【讨论】:

          【解决方案12】:

          只使用 bash、wc 和 sed:

          #! /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
          

          【讨论】:

          • 抱怨缺少引用,echo -n 其中printf '%s -&gt; ' "$v" 本来是更便携的选择
          猜你喜欢
          • 1970-01-01
          • 2018-09-25
          • 2017-05-03
          • 2020-04-01
          • 1970-01-01
          • 1970-01-01
          • 2014-02-21
          • 1970-01-01
          • 2013-05-08
          相关资源
          最近更新 更多