58

次の単純なバージョン管理スクリプトは、特定のファイルの最後のバージョン番号を見つけてインクリメントし、新しく作成されたファイル (エディターなど) で特定のコマンドを実行してから、安定版に保存することを目的としています。シンプルなので、スクリプトは必要に応じて変更されるため、何もチェックしません。たとえば、結果が安定しない場合、ユーザーは最後の引数を省略できます。

ただし、現在の機能の主な懸念事項の 1 つは、以下を実装する方法です。1 のみの場合は、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.

更新: 以下は私の PC で動作します。未解決の問題の改善または解決策が見つかった場合は更新します。

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

提供されたさまざまなソリューションに感謝しています。それらは、視点を得て比較するのに役立つからです。

4

12 に答える 12

71
$ 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
于 2011-12-28T08:51:25.967 に答える
32

より柔軟なオプションがいくつかあります。どちらも、インクリメントする位置を示す 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

明らかに、これはバージョン文字列をインクリメントするだけでは過剰です。しかし、私がこれを書いたのは、さまざまな種類のプロジェクトが必要だったからです。また、速度が問題でなければ、何十ものスクリプトで同じコードを微調整するよりも再利用性を好むためです。それは私のオブジェクト指向側が私のスクリプトに漏れているだけだと思います。

于 2013-06-28T12:21:04.433 に答える
32

これは、接尾辞もサポートするさらに短いバージョンです (-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- 最後の桁の後のもの
于 2014-02-01T01:38:39.310 に答える
12

ピュアバッシュ:

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

バージョン文字列は分割され、配列部分に格納されます。ループは、バージョンの最後の部分から最初の部分に移動します。最後の部分はインクリメントされ、場合によっては元の長さに短縮されます。キャリーは次のパートに持ち込まれます。

于 2011-12-28T17:44:16.813 に答える
1

バッシュにうんざりしていませんか?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

もちろん、要件に完全には準拠していません。

于 2011-12-29T18:38:46.953 に答える
1

ソフトウェア プロジェクトのバージョン番号の決定は、相対的な変更 / 機能 / 開発段階 / リビジョンに基づいています。バージョン番号とリビジョン番号の結果としてのインクリメントは、理想的には人間が行うべきプロセスです。ただし、このスクリプトを作成する動機を推測しないでください。ここに私の提案があります。

要件で説明したことを正確に実行するロジックをスクリプトに含めます。

"...ドットの後の最後のセクションが 2 桁の場合、99 までインクリメントします。1 のみの場合、9 までインクリ..."

3 番目の位置が開発ステージ番号$dNumで、4 番目 (最後) の位置がリビジョン番号であると仮定し$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"

バージョン管理規則について詳しく知りたい場合は、この SO ディスカッションこのウィキペディアのエントリがお役に立てば幸いです。

于 2011-12-28T07:57:02.967 に答える
0

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
于 2011-12-28T11:06:25.903 に答える