resueltos - scripts bash ejemplos
¿Cómo incrementar el número de versión en un script de shell? (7)
El siguiente script de control de versión simple está destinado a encontrar el último número de versión de un archivo dado, a incrementarlo, a ejecutar un comando dado con el archivo recién creado (por ejemplo, al editor), y luego guardarlo en estable. Ya que es simple, no comprueba nada ya que el script se modificará según sea necesario. Por ejemplo, si el resultado no será estable, el usuario puede omitir el último argumento.
Sin embargo, una de las principales preocupaciones de la funcionalidad actual es cómo implementar lo siguiente: si la última sección después del punto tiene dos dígitos, hasta 99; si solo es 1, inc hasta 9, luego pase a la sección anterior. Las versiones pueden tener cualquier número entero positivo de secciones.
1.2.3.44 -> 1.2.3.45
1.2.3.9 -> 1.2.4.0
1.2.3 -> 1.2.4
9 -> 10
El problema restante es que no espera a que un editor de vino con pestañas cierre el archivo; El objetivo es detectar cuándo se cierra la pestaña. Además, ¿podría explicar cuál es la mejor manera de asegurarse de que los nombres de mis variables no sobrescriban los existentes?
También puedes ofrecer otras mejoras.
#!/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.
Actualización: Lo siguiente funciona en mi PC, lo actualizaré si se encuentran mejoras o soluciones a problemas no resueltos:
#!/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.
Aprecio la variedad de soluciones que se han ofrecido, ya que ayudan a obtener una perspectiva y hacer una comparación.
Aquí hay un par de opciones más flexibles. Ambos aceptan un segundo argumento para indicar qué posición incrementar.
1. Función simple
Para una entrada más predecible.
# 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. Función robusta.
Para usar con scripts, o más personalización para aplicar a varios sistemas de versiones. Podría usar un par de opciones más, pero tal como está ahora funciona para mis proyectos usando las secuencias de versión "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
Obviamente, esto es excesivo solo para incrementar una cadena de versión. Pero escribí esto porque necesitaba diferentes tipos de proyectos, y porque si la velocidad no es un problema, prefiero la reutilización en lugar de modificar el mismo código en docenas de scripts. Supongo que ese es mi lado orientado a objetos que se filtra en mis scripts.
Aquí hay una versión aún más corta que también admite un postfix (agradable para -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
Explicación
Usé regex para capturar 3 partes. Las cosas antes de la última posición, el número a incrementar y las cosas después.
-
((/d+/.)*)
- cosas del 1.1.1.1.1. -
(/d+)
- el último dígito -
(.*)
- las cosas después del último dígito
Luego uso la opción e para permitir expresiones en la parte de reemplazo. Tenga en cuenta que con la opción e / 1 se convierte en una variable $ 1 y debe concatenar las variables con el operador de puntos.
-
$1
- el grupo de captura de 1.1.1.1.1. -
($3+1)
- incrementa el último dígito. nota $ 2 se usa en el subgrupo de $ 1 para obtener el 1 repetido. -
$4
- las cosas después del último dígito
Cansado de bash? ¿Por qué no probar 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
Por supuesto, no cumple con el requisito.
La determinación de un número de versión para un proyecto de software se basa en su cambio relativo / funcionalidad / etapa de desarrollo / revisión. Los incrementos consecuentes en la numeración de la versión y la revisión es idealmente un proceso que debe ser realizado por un humano. Sin embargo, para no adivinar su motivación para escribir este guión, esta es mi sugerencia.
Incluya algo de lógica en su script que haga exactamente lo que usted describe en su requerimiento
"... si la última sección después del punto tiene dos dígitos, inc hasta 99; si solo es 1, inc hasta 9 ..."
Suponiendo que la tercera posición es el número de etapa de desarrollo $dNum
y la cuarta (última) posición es el número de revisión $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
Tal vez una función permita la forma más sucinta de manejar todas las posiciones (majNum.minNum.dNum.rNum).
Tendrá que separar el nombre del proyecto y los componentes del número de versión de su nombre de archivo en su script y luego construir el número de versión con todas sus posiciones, y finalmente reconstruir el nombre de archivo con algo como
new_version="$majNum.minNum.$dNum.$rNum"
new_version_file="$old_file.$new_version"
Espero que ayude y verifique esta discusión de SO así como esta entrada de wikipedia si desea saber más acerca de las convenciones de versiones.
Pure Bash:
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
resultado:
1.2.3.45
La cadena de versión se divide y se almacena en la parte de la matriz. El bucle va desde la última hasta la primera parte de la versión. La última parte se incrementará y posiblemente se reducirá a su longitud original. Se lleva un carry a la siguiente parte.
Usando sólo bash, wc y 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 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
ACTUALIZAR:
#!/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