String manipulation in .gitlab-ci variables - gitlab-ci

I'm trying to set up my ci file with some variables. I'm able to generate a variable like so;
...
variables:
TARGET_PROJECT_DIR: "${CI_PROJECT_NAME}.git"
However, I don't seem to be able to do this;
...
variables:
PROJECT_PROTOCOL_RELATIVE_URL: "${CI_PROJECT_URL//https:\/\/}.git"
If I run that in bash, I get the expected output which is gitlab.com/my/repo/url.git with the 'https://' removed and the '.git' appended.
My workaround has just been to export it in the 'script' section, but it feels a lot neater to add this to the variables section, since this is part of a template that is being inherited by the actual jobs. Is it possible?

There are several more useful variables defined in the GitLab CI environment.
CI_PROJECT_PATH gives you the <namespace>/<project name> (or just <project name> if you have no extra namespace) string and
CI_SERVER_HOST gives you the server name, so you could do
variables:
PROJECT_PROTOCOL_RELATIVE_URL: ${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git
I have similar setups (also without quotes).
I'm not sure if that will work for you, since my runners and my server are under my control and I don't run pipelines with external projects.
But you can get all available variables displayed in the job log by running a job like this:
stages:
- env
show-env:
stage: env
script:
- env
Also always helpful is https://docs.gitlab.com/ee/ci/variables/predefined_variables.html

After looking around for similar challenges I found your not answered question. Here are my suggestions:
stages:
- todo
todo-job:
stage: todo
only:
- master
script:
#your question / example
- echo ${CI_PROJECT_URL}
- echo ${CI_PROJECT_URL:8:100}.git
#Because you have the word manipulation in the title, I have some more examples:
#Return substring between the two '_'
- INPUT="someletters_12345_moreleters.ext"
- SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `
- echo $SUBSTRING
#Store a substring in a new variable and create an output
- b=${INPUT:12:5}
- echo $b
#Substring using grep with regex (more readable)
- your_number=$(echo "someletters_12345_moreleters.ext" | grep -E -o '[0-9]{5}')
- echo $your_number
#Substring using variable and 'grep' with regex (more readable)
- your_number=$(echo "$INPUT" | grep -E -o '[0-9]{5}')
- echo $your_number
#split a string and return a part using 'cut'
- your_id=$(echo "Release V14_TEST-42" | cut -d "_" -f2 )
- echo $your_id
#split the string of a variable and return a part using 'cut'
- VAR="Release V14_TEST-42"
- your_number=$(echo "$VAR" | cut -d "_" -f2 )
- echo $your_number
Gitlab output looks like:
$ echo ${CI_PROJECT_URL}
https://gitlab.com/XXXXXXXXXX/gitlab_related_projects/test
$ echo ${CI_PROJECT_URL:8:100}.git
gitlab.com/XXXXXXXXXX/gitlab_related_projects/test.git
$ INPUT="someletters_12345_moreleters.ext"
$ SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `
$ echo $SUBSTRING
12345
$ b=${INPUT:12:5}
$ echo $b
12345
$ your_number=$(echo "someletters_12345_moreleters.ext" | grep -E -o '[0-9]{5}')
$ echo $your_number
12345
$ your_number=$(echo "$INPUT" | grep -E -o '[0-9]{5}')
$ echo $your_number
12345
$ your_number=$(echo "Release V14_TEST-42" | cut -d "_" -f2 )
$ echo $your_number
TEST-42
$ VAR="Release V14_TEST-42"
$ your_number=$(echo "$VAR" | cut -d "_" -f2 )
$ echo $your_number
TEST-42
Cleaning up project directory and file based variables
00:01
Job succeeded

Related

Gitlab CI variables and script section give different results

I've searched a lot of examples but they did not work for me.
I'm trying to run linters for changed files when MR is opened.
My .gitlab-ci.yml
run_linters:
image: python:3
variables:
FILES: git diff --name-only $CI_MERGE_REQUEST_TARGET_BRANCH_NAME | grep *.py
before_script:
- python3 -m pip install black==21.5b1
- python3 -m pip install flake8==3.9.2
script:
- echo $FILES
- git diff --name-only $CI_MERGE_REQUEST_TARGET_BRANCH_NAME | grep *.py
- black --check $FILES
- flake8 $FILES
only:
- merge_requests
And I'm getting strange output.
echo $FILES says git diff --name-only main | grep incoming_file.py
incoming_file.py is the only file in that MR. Why is it around grep?
And git diff at script section says fatal: ambiguous argument 'main': unknown revision or path not in the working tree.
Why is filename present around grep?
Why are same git diff commands give different result?
Why is filename present around grep?
In bash when you refer to * this will expand and try to match the files/directories present in your current path, in your case since only the incoming_file.py is present, so it expands to this.
Why are same git diff commands give different result?
variables:
FILES: git diff --name-only $CI_MERGE_REQUEST_TARGET_BRANCH_NAME | grep *.py
When you define a variable in variables section, Gitlab doesnt execute the command, it simple populates the variable FILES with the string git diff --name-only $CI_MERGE_REQUEST_TARGET_BRANCH_NAME | grep *.py
Then in the script section, the runner expands *.py to incoming_file.py and $CI_MERGE_REQUEST_TARGET_BRANCH_NAME to main
that's why in echo you see git diff --name-only main | grep incoming_file.py
Here
- git diff --name-only $CI_MERGE_REQUEST_TARGET_BRANCH_NAME | grep *.py
You actually execute the command and you get the mentioned message

Make work find pipe awk command in Makefile

I have this find awk line to get python code analyse::
$ find ./ -name '*.py' -exec wc -l {} \; | sort -n| awk '{print $0}{s+=$0}END{print s}'
12 ./gb/__init__.py
23 ./gb/value_type.py
40 ./setup.py
120 ./gb/libcsv.py
200
$
I try to put it in a Makefile::
$ cat Makefile
python_count_lines: clean
#find ./ -name '*.py' -exec wc -l {} \; | sort -n| awk '{print \$0}{s+=\$0}END{print s}'
But this did not work::
$ make python_count_lines
awk: line 1: syntax error at or near }
Makefile:12: recipe for target 'python_count_lines' failed
make: *** [python_count_lines] Error 2
$
Bertrand Martel is correct that you need to escape dollar signs from make by doubling them, not prefixing them with backslashes (see info here).
However, the rest of that suggestion is not right and won't work; first, you should almost never use the shell function in a recipe. Second, using the info function here cannot work because in the first line you've set a shell variable RES equal to some value, then you try to print the make variable RES in the second line; not only that but each line is run in a separate shell, and also all make variable and function references are expanded up-front, before any part of the recipe is passed to the shell.
You just need to do this:
python_count_lines: clean
#find ./ -name '*.py' -exec wc -l {} \; | sort -n| awk '{print $$0}{s+=$$0}END{print s}'

Modify zsh commands to forward errors

I would like to modify one of my recent Bash aliases to forward errors. Here is the alias:
alias makecclip=
"make |& tee >(sed \"s,\x1B\[[0-9;]*[a-zA-Z],,g\" |
egrep \":[0-9]+:[0-9]+: error\" | cut -d : -f1,2,3 |
head -n 1 | xargs -0 echo -n | xclip -selection clipboard &&
xclip -selection clipboard -o)
This code displays the results of a C++ compilation, and then removes formatting and displays and adds to the clipboard the first error location (if there is any).
However, I would like to use this code like this:
makecclip && bin/someexecutablecreated
This though ruins the && operator, since it always runs bin/someexecutablecreated even when there is a compilation error present. How can I add modifications to the code to set the error flag, when the error list (the things saved to clipboard and echoed) is not empty?
You can address your issue by using the PIPESTATUS internal variable (this variable has other names in non-bash shells). This allows to have an history of exit statuses of commands passed by pipe.
You precised in the comments that you didn't use bash, but used zsh instead. As such, some of the syntax of my solution has to be changed, as they handle the PIPESTATUS variable differently.
In bash, you use ${PIPESTATUS[0]}, whereas you'll use ${pipestatus[1]} in zsh.
A first approach, using your existing alias, could be as follow :
makecclip && [ "${pipestatus[1]}" -eq "0" ] && echo "ok"
This runs the echo command only if "${pipestatus[1]}" is equal to 0 (no errors during make)
A more convenient solution would be to use a function instead of an alias for makecclip. In your ~/.bashrc file, you could write :
makecclip () {
make |& tee >(sed "s,\x1B\[[0-9;]*[a-zA-Z],,g" | egrep ":[0-9]+:[0-9]+: error" | cut -d : -f1,2,3 | head -n 1 | xargs -0 echo -n | xclip -selection clipboard && xclip -selection clipboard -o)
return "${pipestatus[1]}"
}
Now, makecclip && echo "ok" will work as expected.
Test cases :
#!/bin/zsh
#do not run this test if there is an existing makefile in your current directory
rm -f makefile
makecclip () {
make |& tee >(sed "s,\x1B\[[0-9;]*[a-zA-Z],,g" | egrep ":[0-9]+:[0-9]+: error" | cut -d : -f1,2,3 | head -n 1 | xargs -0 echo -n | xclip -selection clipboard && xclip -selection clipboard -o)
# this part is only present to check the pipestatus values during the tests.
# In the real function, I wrote 'return ${pipestatus[1]}' instead.
a=(${pipestatus[#]})
echo ${a[#]}
return ${a[1]}
}
echo "# no makefile"
makecclip && echo "ok"
echo -e "\n# empty makefile"
touch makefile
makecclip && echo "ok"
echo -e "\n# dummy makefile entry"
echo -e 'a:\n\t#echo "inside makefile"' > makefile
makecclip && echo "ok"
echo -e "\n# program with error makefile"
echo -e "int main(){error; return 0;}" > target.cc
echo -e 'a:\n\tgcc target.cc' > makefile
makecclip && echo "ok"
Output :
$ ./test.sh
# no makefile
make: *** No targets specified and no makefile found. Stop.
2 0
# empty makefile
make: *** No targets. Stop.
2 0
# dummy makefile entry
inside makefile
0 0
ok
# program with error
gcc target.cc
target.cc: In function ‘int main()’:
target.cc:1:12: error: ‘error’ was not declared in this scope
int main(){error; return 0;}
^
makefile:2: recipe for target 'a' failed
make: *** [a] Error 1
target.cc:1:12
2 0

A script to change file names

I am new to awk and shell based programming. I have a bunch of files name file_0001.dat, file_0002.dat......file_1000.dat. I want to change the file names such as the number after file_ will be a multiple of 4 in comparison to previous file name. SO i want to change
file_0001.dat to file_0004.dat
file_0002.dat to file_0008.dat
and so on.
Can anyone suggest a simple script to do it. I have tried the following but without any success.
#!/bin/bash
a=$(echo $1 sed -e 's:file_::g' -e 's:.dat::g')
b=$(echo "${a}*4" | bc)
shuf file_${a}.dat > file_${b}.dat
This script will do that trick for you:
#!/bin/bash
for i in `ls -r *.dat`; do
a=`echo $i | sed 's/file_//g' | sed 's/\.dat//g'`
almost_b=`bc -l <<< "$a*4"`
b=`printf "%04d" $almost_b`
rename "s/$a/$b/g" $i
done
Files before:
file_0001.dat file_0002.dat
Files after first execution:
file_0004.dat file_0008.dat
Files after second execution:
file_0016.dat file_0032.dat
Here's a pure bash way of doing it (without bc, rename or sed).
#!/bin/bash
for i in $(ls -r *.dat); do
prefix="${i%%_*}_"
oldnum="${i//[^0-9]/}"
newnum="$(printf "%04d" $(( 10#$oldnum * 4 )))"
mv "$i" "${prefix}${newnum}.dat"
done
To test it you can do
mkdir tmp && cd $_
touch file_{0001..1000}.dat
(paste code into convert.sh)
chmod +x convert.sh
./convert.sh
Using bash/sed/find:
files=$(find -name 'file_*.dat' | sort -r)
for file in $files; do
n=$(sed 's/[^_]*_0*\([^.]*\).*/\1/' <<< "$file")
let n*=4
nfile=$(printf "file_%04d.dat" "$n")
mv "$file" "$nfile"
done
ls -r1 | awk -F '[_.]' '{printf "%s %s_%04d.%s\n", $0, $1, 4*$2, $3}' | xargs -n2 mv
ls -r1 list file in reverse order to avoid conflict
the second part will generate new filename. For example: file_0002.dat will become file_0002.dat file_0008.dat
xargs -n2 will pass two arguments every time to mv
This might work for you:
paste <(seq -f'mv file_%04g.dat' 1000) <(seq -f'file_%04g.dat' 4 4 4000) |
sort -r |
sh
This can help:
#!/bin/bash
for i in `cat /path/to/requestedfiles |grep -o '[0-9]*'`; do
count=`bc -l <<< "$i*4"`
echo $count
done

Whats wrong with this C shell script?

I am trying to write a C shell equivalent script for the bash script mentioned here.
This is what I have :
#! /bin/tcsh
set now=`date +%Y%m%d%H%M.%S`
if (( ! -f "./cache" ) || (-n "`find ./monme -newer ./cache`" ))
then
touch cache -t "$now"
echo "new files added" | mail -s "new build" myemail#myserver.com
endif
and this is the error I get
$ ./scr
if: Badly formed number.
$
This page mentions that "Numbers in the C-shell must be integers", so I tried
set now=`date +%Y%m%d%H%M`
but I get the same error still.
I cut down your script to this:
#! /bin/tcsh
if ( -n "`find ./monme -newer ./cache`" ) then
echo hello
endif
This gives the same error. I think the culprit is
-n "`find ./monme -newer ./cache`"
What is -n supposed to do? I think it wants a number, but gets something else...
Update: -n in bash means "length of string is non-zero". In my version of tcsh it is as easy to replace as to use == "" like this:
if (( ! -f "./cache" ) || ("`find ./monme -newer ./cache`" != ""))
then
touch cache -t "$now"
echo "new files added" | mail -s "new build" myemail#myserver.com
endif
Try that and see if it works.