This question already has answers here:
How to increment version number in a shell script?
(14 answers)
Closed 8 years ago.
I use the following bash/shell script to semi-automate the git add/commit/push routine on my project:
git_push.sh
#!/bin/bash
# Mini Config
RColor='\e[0m'
Red='\e[0;31m';
Green='\e[0;32m';
Yellow='\e[0;33m'
# Change To Working Directory
clear;
cd $HOME/public_html/iwms_reboot;
# Get Git Commit Notes
echo -en "\r\n${Green}Enter commit notes: ${Yellow}";
read notes;
if [[ -z "$notes" ]]
then
echo -e "\r\n${Red}ERROR: You have not entered any git commit notes.${RColor}\r\n";
exit 0;
fi
echo -e "${RColor}";
# Git Add, Commit & Push
git add .;
git commit -m "${notes}";
echo -e "\r\n";
git push;
echo -e "\r\n";
This works perfectly fine.
I want to take this one step further. On my prject, there is a single file called version.php with the following lines of code:
<?php
// Script Version
$script_version = 'v1.0.5';
?>
My question is, is it possible to use bash/shell scripting to load this file's content and find the number after the 2nd period (.) and increment it by one?
i.e. v1.0.5 will become v1.0.6
This way, I can run this version number updating function before my (git add/commit/push) routine to implement an automatic minor version number update functionality on my project. I.e. script version number goes up automatically every time I commit.
If you want a 'pure-bash' solution, here it is...:
#!/bin/bash
new_version=''
increment_version_number () {
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[*]}"
new_version="${new// /.}";
}
version=$(sed version.php -e "s/\$script_version = 'v//" | sed -e "s/';$//")
increment_version_number $version
echo $new_version;
UPDATE:
Code for a two digits version numbers (as requested in comment):
#!/bin/bash
new_version=''
increment_version_number () {
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+1)) ] && carry=1 || carry=0
part[CNTR]=${new}
done
new="${part[*]}"
new_version="${new// /.}";
}
version=$(grep "\$script_version" version.php | sed -e "s/\$script_version = 'v//" | sed -e "s/';$//")
increment_version_number $version
echo $new_version;
(warning: not fully tested code...)
Thanks to fredtantini and using the answer from How to increment version number in a shell script?, I have come up with the following solution to my original problem.
I first created a file called version.data and put the text 1.0.5 in it.
Then I updated my PHP script like so:
<?php
// Script Version
$script_version = 'v'. trim(file_get_contents(app_path() .'/version.data'));
?>
Then I have created the following gawk script called version_updater.sh (next to my git_push.sh script):
#!/usr/bin/gawk -f
BEGIN {
printf("%s", inc(ARGV[1]))
}
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]
}
Then I have updated my git_push.sh script like so:
#!/bin/bash
# Mini Config
RColor='\e[0m'
Red='\e[0;31m';
Green='\e[0;32m';
Yellow='\e[0;33m';
Source=$HOME/public_html/iwms_reboot;
# Increment Script Version
CurrentVersion=`cat "$Source/app/version.data"`;
NewVersion=`./version_updater.sh $CurrentVersion`;
# Change To Working Directory
clear;
cd $Source;
# Get Git Commit Notes
echo -en "\r\n${Green}Enter commit notes: ${Yellow}";
read notes;
if [[ -z "$notes" ]]
then
echo -e "\r\n${Red}ERROR: You have not entered any git commit notes.${RColor}\r\n";
exit 0;
fi
echo -e "${RColor}";
# Update Script Version
echo $NewVersion > $Source/app/version.data;
# Git Add, Commit & Push
git add .;
git commit -m "${notes}";
echo -e "\r\n";
git push;
echo -e "\r\n";
... and that's it, it works.
when the script runs, it reads the current version, passes it to the version_updater.sh to programatically increment it and put the return value into a variable.
Just before I commit, I update my version.data file with the updated version number. Now when I commit, I commit with the new version number.
Related
I have a folder named students_projects that contains 10 subfolders. Each subfolder has project1.c and project2.c, each one prints a value. I made a bash script function that check those numbers and save them to a txt file. I tested it with 2 .c files named project1 and project2 in Desktop and not in the folder. I made sure that i worked fine but when i went to run it for all the subfolders it keeps saving the values that are in desktop files.
My function:
function test () {
gcc project1.c
p1=$(./a.out)
if (( $p1 == 20 ));
then
v1=30
else
v1=0
fi
gcc project2.c
p2=$(./a.out)
if (($p2 == 10 ));
then
v2=70
else
v2=0
fi
sum=$(( $v1 + $v2 ))
on="cut -d' ' -f1 report.txt"
onoma=$(eval "$on")
temp="cut -d' ' -f2 report.txt"
am=$(eval "$temp")
printf "$onoma $am project1: $v1 project2: $v2 total_grade: $sum\n" >> grades.txt
}
Then i do
for FILE in students_projects/* ; do
test
done
Looks like the grades.txt file is at the top level, but you want to run test under each directory. The easiest change is to pass a full path to $PWD/grades.txt to the function. Coombine that with running "cd" into each subdir. Recommendation:
for FILE in students_projects/* ; do
(cd $FILE; test $PWD/grades.txt)
done
Then in your "function test()", use that $1 argument instead of grades.txt:
printf "..." >> $1
I'm working on a shell program to automatise my Arch (mandatory btw) installation. To make it more interactive, I've built the following function:
# READYN
# ARGS:
# - Yes/no question
# - Command to run if yes
# - Command to run if no
#
# Prompts the user with a yes/no question (with precedence for yes) and
# run an order if the answer is yes or another if it's no.
readyn () {
while :
do
local yn;
printf "%s? [Y/n]: " "$1";
read yn;
if [[ "$yn" =~ ^([yY][eE][sS]|[yY])?$ ]]; then
$2;
break;
elif [[ "$yn" =~ ^([nN][oO]|[nN])+$ ]]; then
$3;
break;
fi
done
}
I've succeeded in passing an "echo Hello World!" as an argument and having it run. I've also been able to pass another function. For example:
yayprompt () {
printf "yay is required to install %s.\n" "$1"
readyn "Install yay, the AUR manager" "yayinstall" ""
}
This calls yayinstall if yes and does nothing if no.
My problem comes with more complex functions, which are passed as arguments but are either not recognised or run when they're not supposed to. The problem comes with the following function:
# MANAGEPGK
# ARGS:
# - Package name
# - Package variable
# - Yay required
#
# Checks if the package is added to the pkglist to either add or remove it.
# If yay is required to install it, it prompts the user whether they wish
# to install yay or don't install the package (if yay is not installed).
# This functions DOES NOT prompt any installation options on the user. To
# do this, use PROMPTPKG.
managepkg () {
local pkgvar=$2
if [ $pkgvar == 0 ]; then
if [ $3 == 1 ] && [ $yay == 0 ]; then
yayprompt;
fi
if [ $3 == 0 ] || [ $yay == 1 ]; then
addpkg "$1";
pkgvar=1;
fi
else
rmpkg "$1";
pkgvar=0;
fi
echo "$pkgvar";
}
For it to work properly, it has to (or at least I've had to) be called like this:
dewm_cinnamon=$(managepkg cinnamon $dewm_cinnamon 0)
Now, I'm trying to pass it as an argument to readyn, but I'm having these outputs depending on the format (I'm always answering yes as empty string:
Simple quotes:
readyn "Install gaps" \
'dewm_i3gaps=$(managepkg i3-gaps $dewm_i3gaps 0)' \
'dewm_i3=$(managepkg i3-wm $dewm_i3 0)';
Install gaps? [Y/n]:
./architup.sh: line 341: dewm_i3gaps=$(managepkg: command not found
Double quotes:
readyn "Install gaps" \
"dewm_i3gaps=$(managepkg i3-gaps $dewm_i3gaps 0)" \
"dewm_i3=$(managepkg i3-wm $dewm_i3 0)";
Install gaps? [Y/n]:
./architup.sh: line 341: dewm_i3gaps=1: command not found
Dollar enclosed: (This one runs both commands as seen in cat pkglist)
readyn "Install gaps" \
$(dewm_i3gaps=$(managepkg i3-gaps $dewm_i3gaps 0)) \
$(dewm_i3=$(managepkg i3-wm $dewm_i3 0));
Install gaps? [Y/n]:
Install compton? [Y/n]: ^C
Documents/Repositories/architup took 5s
➜ cat pkglist
i3-gaps
i3-wm
What syntax should I use to have readyn run only one command based on the user input?
Thank you!
Function arguments are just strings. A better design IMHO is to simply have readyn return true (zero) for "yes" and false otherwise, and have the calling code implement any conditional logic based on that.
readyn () {
read -p "$#"
case $REPLY in
[Yy] | [Yy][Ee][Ss]) return 0;;
esac
return 1
}
readyn "Are you ready San Antonio?" &&
rock and roll
if readyn "Let me hear you say yeah"; then
echo "Let's go!"
else
echo "If you feel mellow, get outta here"
fi
(With apologies to rock concerts everywhere,)
Need some help here. I need to create an executable file for every user that exists on the system ( Linux ) and the format for file is the following :
fis_nr_username
where nr stands for 1st file, 2nd file etc...
EXAMPLE OF SITUATION
Users on machine :
stud01
stud02
stud03
I need a file for each of them to be executable and look like this :
file_1_stud01
file_2_stud02
file_3_stud03
You could loop through the user list, then loop through file number (here 0 to 10). Use printf with %03d to pad with zeros.
#!/usr/bin/env bash
username="stud01 stud02 stud03"
for name in $username; do
for ((i=0; i<11; i++)); do
printf "file_%03d_%s\n" $i $name
done
done
You could make this a function and put it in .bashrc
newfiles() {
username="$#"
for name in $username; do
for ((i=0; i<3; i++)); do
printf "file_%03d_%s\n" $i $name
done
done
}
call the function from terminal with: newfiles firstuser serconduser. Output:
fis_000_firstuser
fis_001_firstuser
fis_002_firstuser
fis_000_seconduser
fis_001_seconduser
fis_002_seconduser
Having bash, created simple scripts for accessing array element by it's index.It as follows
#! /bin/bash
OK_INDEX=0
CANCEL_INDEX=1
ERROR_INDEX=2
CONFIRM_INDEX=3
SAVE_INDEX=4
EXIT_INDEX=5
declare -a messageList=("ok"
"cancel"
"error"
"confirm"
"save"
"exit")
printf "%s \n" ${messageList[$CANCEL_INDEX]}
from above scripts i need to declare proper index variable to retrieve valid message from array list but it likely not handy for me to declare each variable and give index to them.It is nice if variable autometically getting value as like in C for ENUM data type
in C it's possible by like
enum index { OK_INDEX, CANCEL_INDEX, ERROR_INDEX,CONFIRM_INDEX,SAVE_INDEX,EXIT_INDEX};
is there any alternative for ENUM in bash?
I found lot but not succeded then have try some trick to achieve this it is as follows
ENUM=(OK_INDEX CANCEL_INDEX ERROR_INDEX CONFIRM_INDEX SAVE_INDEX EXIT_INDEX)
maxArg=${#ENUM[#]}
for ((i=0; i < $maxArg; i++)); do
name=${ENUM[i]}
declare -r ${name}=$i
done
So form above code snippet i successfully created constant but it seems lengthy means just declaring variable i need to write 5-10 lines code which is not fair.
So any one have another solution?
Try the following fragment of code ... I guess that it is what you want
#!/bin/bash
set -u
DEBUG=1
# This funcion allow to declare enum "types", I guess
enum ()
{
# skip index ???
shift
AA=${###*\{} # get string strip after {
AA=${AA%\}*} # get string strip before }
AA=${AA//,/} # delete commaa
((DEBUG)) && echo $AA
local I=0
for A in $AA ; do
eval "$A=$I"
((I++))
done
}
### Main program
# Just declare enum as you need
enum index { OK_INDEX, CANCEL_INDEX, ERROR_INDEX, CONFIRM_INDEX, SAVE_INDEX, EXIT_INDEX };
# Print value of enumerated items
echo $OK_INDEX
echo $CANCEL_INDEX
echo $ERROR_INDEX
echo $CONFIRM_INDEX
echo $SAVE_INDEX
echo $EXIT_INDEX
# Use enumerated index in program
I=CONFIRM_INDEX
case $I in
OK_INDEX )
echo "Process here when index is $I"
;;
CANCEL_INDEX )
echo "Process here when index is $I"
;;
ERROR_INDEX )
echo "Process here when index is $I"
;;
CONFIRM_INDEX )
echo "Process here when index is $I"
;;
SAVE_INDEX )
echo "Process here when index is $I"
;;
EXIT_INDEX )
echo "Process here when index is $I"
;;
esac
exit 0
My take on this:
function \
_enum()
{
## void
## (
## _IN $# : [ array<string> ] list
## )
local list=("$#")
local len=${#list[#]}
for (( i=0; i < $len; i++ )); do
eval "${list[i]}=$i"
done
}
Example:
ENUM=(
OK_INDEX
CANCEL_INDEX
ERROR_INDEX
CONFIRM_INDEX
SAVE_INDEX
EXIT_INDEX
) && _enum "${ENUM[#]}"
echo "OK_INDEX = "$OK_INDEX
echo "CANCEL_INDEX = "$CANCEL_INDEX
echo "ERROR_INDEX = "$ERROR_INDEX
echo "CONFIRM_INDEX = "$CONFIRM_INDEX
echo "SAVE_INDEX = "$SAVE_INDEX
echo "EXIT_INDEX = "$EXIT_INDEX
Output
OK_INDEX = 0
CANCEL_INDEX = 1
ERROR_INDEX = 2
CONFIRM_INDEX = 3
SAVE_INDEX = 4
EXIT_INDEX = 5
I find this to be the cleanest and most straightforward approach.
Another solution is to assign values to an associative array to make an enum set with the variable name as the prefix. This allows introspection of the enum by walking through all available values and their associated key names:
function \
_enum_set()
{
## void
## (
## _IN $1 : [ string ] prefix
## _IN ... : [ array<string> ] list
## )
local prefix=$1
local list=("$#")
local len=${#list[#]}
declare -g -A $prefix
for (( i=0; i < $len; i++ )); do
# Skip the first argument
[[ $i = 0 ]] &&
continue
eval "$prefix[${list[$i]}]=$(( $i - 1 ))"
done
}
Example (looping):
ENUM=(
OK
CANCEL
ERROR
CONFIRM
SAVE
EXIT
) && _enum_set ENUM_INDEX "${ENUM[#]}"
echo ""
for i in "${!ENUM_INDEX[#]}"; do
echo "ENUM_INDEX[$i] = "${ENUM_INDEX[$i]}
done
Output:
ENUM_INDEX[CONFIRM] = 3
ENUM_INDEX[OK] = 0
ENUM_INDEX[EXIT] = 5
ENUM_INDEX[ERROR] = 2
ENUM_INDEX[SAVE] = 4
ENUM_INDEX[CANCEL] = 1
Example (explicit):
ENUM=(
OK
CANCEL
ERROR
CONFIRM
SAVE
EXIT
) && _enum_set ENUM_INDEX "${ENUM[#]}"
echo "ENUM_INDEX[OK] = "${ENUM_INDEX[OK]}
echo "ENUM_INDEX[CANCEL] = "${ENUM_INDEX[CANCEL]}
echo "ENUM_INDEX[ERROR] = "${ENUM_INDEX[ERROR]}
echo "ENUM_INDEX[CONFIRM] = "${ENUM_INDEX[CONFIRM]}
echo "ENUM_INDEX[SAVE] = "${ENUM_INDEX[SAVE]}
echo "ENUM_INDEX[EXIT] = "${ENUM_INDEX[EXIT]}
Output:
ENUM_INDEX[OK] = 0
ENUM_INDEX[CANCEL] = 1
ENUM_INDEX[ERROR] = 2
ENUM_INDEX[CONFIRM] = 3
ENUM_INDEX[SAVE] = 4
ENUM_INDEX[EXIT] = 5
Note that associative arrays have no defined order but can always be sorted at a later point.
You can consolidate some of the lines:
$ for i in \
E_OK E_CANCEL E_ERROR E_CONFIRM E_SAVE E_EXIT; do \
readonly ${i}=$((x++)); done
$ for i in ${!E_#}; do echo $i=${!i}; done
Another version using mapfile callback function:
#!/bin/bash
mapfile -t -c 1 -C 'f(){ readonly $2=$1; }; f' << \
EOF
E_OK
E_CANCEL
E_ERROR
E_CONFIRM
E_SAVE
E_EXIT
EOF
for i in ${!E_#}; do echo $i=${!i}; done
Output:
E_CANCEL=1
E_CONFIRM=3
E_ERROR=2
E_EXIT=5
E_OK=0
E_SAVE=4
The typical workaround when an enum is wanted is to use normal strings. In these cases I even omit the otherwise mandatory quotes around variable evaluation:
state=IDLE
...
while [ $state = IDLE ]
do
...
if condition
then
state=BUSY
fi
...
if condition2
then
state=ERROR
fi
...
done
if [ $state = ERROR ]
then
...
fi
This way, of course, you have just the basic functionality of named states and neither of the following typically associated features of enums:
declaration of all possible values (self-documenting code)
associated number for each value (matter of taste if this is a feature or a wart)
no prevention/detection of mistypings (but this is rare in scripts anyway)
Hello: I have a lot of files called test-MR3000-1.txt to test-MR4000-1.nt, where the number in the name changes by 100 (i.e. I have 11 files),
$ ls test-MR*
test-MR3000-1.nt test-MR3300-1.nt test-MR3600-1.nt test-MR3900-1.nt
test-MR3100-1.nt test-MR3400-1.nt test-MR3700-1.nt test-MR4000-1.nt
test-MR3200-1.nt test-MR3500-1.nt test-MR3800-1.nt
and also a file called resonancia.kumac which in a couple on lines contains the string XXXX.
$ head resonancia.kumac
close 0
hist/delete 0
vect/delete *
h/file 1 test-MRXXXX-1.nt
sigma MR=XXXX
I want to execute a bash file which substitutes the strig XXXX in a file by a set of numbers obtained from the command ls *MR* | cut -b 8-11.
I found a post in which there are some suggestions. I try my own code
for i in `ls *MR* | cut -b 8-11`; do
sed -e "s/XXXX/$i/" resonancia.kumac >> proof.kumac
done
however, in the substitution the numbers are surrounded by sigle qoutes (e.g. '3000').
Q: What should I do to avoid the single quote in the set of numbers? Thank you.
This is a reproducer for the environment described:
for ((i=3000; i<=4000; i+=100)); do
touch test-MR${i}-1.nt
done
cat >resonancia.kumac <<'EOF'
close 0
hist/delete 0
vect/delete *
h/file 1 test-MRXXXX-1.nt
sigma MR=XXXX
EOF
This is a script which will run inside that environment:
content="$(<resonancia.kumac)"
for f in *MR*; do
substring=${f:7:3}
echo "${content//XXXX/$substring}"
done >proof.kumac
...and the output looks like so:
close 0
hist/delete 0
vect/delete *
h/file 1 test-MR300-1.nt
sigma MR=300
There are no quotes anywhere in this output; the problem described is not reproduced.
or if it could be perl:
#!/usr/bin/perl
#ls = glob('*MR*');
open (FILE, 'resonancia.kumac') || die("not good\n");
#cont = <FILE>;
$f = shift(#ls);
$f =~ /test-MR([0-9]*)-1\.nt/;
$nr = $1;
#out = ();
foreach $l (#cont){
if($l =~ s/XXXX/$nr/){
$f = shift(#ls);
$f =~ /test-MR([0-9]*)-1\.nt/;
$nr = $1;
}
push #out, $l;
}
close FILE;
open FILE, '>resonancia.kumac' || die("not good\n");
print FILE #out;
That would replace the first XXXX with the first filename, what seemed to be the question before change.