I'm really struggling with writing a Bourne shell script. Basically, I have three input formats for a variable called "ref" that I'm trying to detect:
ref="refs/head/.*" (i.e. begins with "refs/head/" I'm interested in the bit at the end, after the slash)
ref="refs/tags/.*" (i.e. begins with "refs/tags/" I'm interested in the bit at the end, after the slash)
everything else (i.e. ignore everything that doesn't begin with either "refs/head/" or "refs/tags/")
For example,
If ref="refs/head/master", set TAG="master"
If ref="refs/tags/0.2.4", set TAG="0.2.4"
For everything else, set TAG=""
Now I wrote something in bash shell, but I'm really struggling to convert it to Bourne (#!/bin/sh):
#!/bin/bash
#
#This works!
#
TAG=""
re='^refs/head/.*' #regex: begins with refs/head/, ends with anything
re2='^refs/tags/.*' #regex: begins with refs/tags/, ends with anything
if [[ $ref =~ $re ]]; then
#do nothing - OK
true #NOP
else
#check if it's a tag update
if [[ $ref =~ $re2 ]]; then
TAG=${$ref##*/} #looks worse that it is: http://stackoverflow.com/questions/3162385/how-to-split-a-string-in-shell-and-get-the-last-field
fi
exit 0
fi
echo $TAG
It took me ages to a) write this program and b) find out why my program was going nuts - turned out I need #!/bin/sh and not #!/bin/bash
How can I convert this to sh? Maybe someone else has a more elegant solution to my regex gymnastics?
Update:
Thanks for the answers sofar (especially #gboffi). I think I'm almost there.
All I need now is to know if $TAG comes from "refs/head/", "refs/tags/" or neither. I tried to modify some of the answers, but really struggling with sh. I'll need to go away and learn more about sh from first principles instead of trying to hack it.
Update 2:
So after a night's sleep, I figured it out in about 20 minutes. Here is my solution:
#!/bin/sh
ref="refs/asdf/master"
TAG=""
TAG="${ref#refs/heads/}"
if [ "$ref" != "${ref#refs/heads/}" ]; then
echo "heads"
echo $TAG
else
TAG="${ref#refs/tags/}"
if [ "$ref" != "${ref#refs/tags/}" ]; then
echo "heads"
echo $TAG
else
TAG=""
fi
fi
echo "--->$TAG"
I'm sure there's a much more elegant solution; but I just don't have the time!
Here it is a function, defined in dash (the linux version of sh)
% dash
$ tag() {
> TAG=""
> [ "$1" != "${1#refs/head/}" ] && TAG="${1#refs/head/}"
> [ "$1" != "${1#refs/tags/}" ] && TAG="${1#refs/tags/}"
> echo "$TAG"
>}
$ tag poldo
$ tag refs/head/poldo
poldo
$ tag refs/tags/pippo
pippo
$ tag to093u0refs/head/poldo
$exit
%
The most idiomatic way to handle this is with a case statement and pattern matching:
#!/bin/sh
case $ref in
refs/head/*) true ;; # original behavior: no output, success
refs/tags/*) printf '%s\n' "${ref##*/}" ;; # original behavior: emit output
*) false # exit with an error # original code didn't specify behavior
esac
The reason I'm exiting with an error for the case where neither refs/head/* or refs/tags/* matches is that if you wanted to exit with success in that case, you could omit the refs/head/* test entirely.
You can use rev-parse for this, here are some examples from one of my
repositories. Notice the last one produces no output, as desired.
$ git rev-parse --abbrev-ref refs/heads/master
master
$ git rev-parse --abbrev-ref refs/tags/5
5
$ git rev-parse --abbrev-ref #^
Having read your description of the desired function it seems that it boils down to this:
check the input argument. If it starts with refs/head or refs/master, proceed, else stop.
take the terminal piece of information and set the variable TAG to it.
thus, assuming your input is in the variable REF,
TAG=""
echo $REF | egrep -q 'refs/head|refs/master'
if [ $? -eq 0 ]
then
TAG=`echo $REF | sed "s/.*\///"`
fi
ought to do it.
(Side note for shell heads: is basename a better solution than sed in this particular case?)
Related
I want to analyse output from a command.
I have 2 potential string matches.
If I match one, the other or both, I class that as a pass.
If the output contains anything else I want to flag it as a fail.
This was my original code, which works most of the time.
But when the output(on a rare occasion) contains both my strings, which is allowed. It flags as a fail:
if [ "$STATUS" != "Ok" ] && [ "$STATUS" != "Non-Critical" ]
then
echo "FAILED: STATUS NOT 'Ok' OR NOT 'Non-Critical'"
else
echo " Status check good."
fi
The above fails for me when my output contains both strings, which is not what I want.
So, to be clear:
Ok - On it's own is a pass.
Non-Critical - On it's own is a pass.
Ok
Non-Critical - is a pass.
Non-Critical
Ok - is a pass.
ANYTHING else is a fail
Hoping someone smarter than me can help :-)
You could use sed for that.
echo -e "Critical\nNon-Critical\nNon-Critical Ok" |
sed 's/.*\(Non-Critical\|Ok\).*/PASS/;t;s/.*/FAIL/'
Explanation: first we replace "Non-Critical" or "Ok" and everything else on that line by PASS; then, the command t jumps to the end of the script upon successful match. If, however, the match is not successful, everything gets replaced by FAIL. Output of the above command:
FAIL
PASS
PASS
How about a case?
case "$STATUS" in
*Non-Critical*) echo PASS;;
*Ok*) echo PASS;;
*) echo FAIL;;
esac
Did I miss something?
Here is a perl version
echo -e "Critical\nNon-Critical\nNon-Critical Ok" | perl -ne 'print((/Non-Critical|Ok/?PASS:FAIL)."\n")'
FAIL
PASS
PASS
For ternary operator a?b:c look here
Also (works only in ksh)
#!/bin/ksh
if [[ $STATUS = #(~(+i:*OK*)) || $STATUS = #(~(+i:*Non-Critical*)) ]]; then
echo "good"
else
echo "bad"
fi
or if you don't want it to be case independant (works both in bash and ksh)
if [[ $STATUS = #(*OK*) || $STATUS = #(*Non-Critical*) ]]; then ............
I am not sure if this was the proper place to post this, none-the-less: I am developing a script to negative match git branches, however when this part was run, it tried to get HTTP headers....can someone explain how this is happening?
test.sh
#!/bin/bash
array_not_contains()
{
local array="$1[#]"
local seeking=$2
local in=0
for element in "${!array}"; do
echo $element #Commenting out this echo will stop it from fetching headers
if [[ $element =~ $seeking ]]; then
in=1
break
fi
done
return $in
}
exclude=() #array that will exclude the following matches from deletion
exclude+=(HEAD)
exclude+=(master)
exclude+=(develop)
exclude+=(example.*)
if $(array_not_contains exclude $1); then
echo "win"
else
echo "fail"
fi
Running it like this:
./test.sh bob
will return headers
It's ending up evaluating HEAD as a command.
bash$ type -all HEAD
HEAD is /usr/bin/HEAD
You probably misunderstand what if $(command) does. It runs command, then runs the output of that as a command, and examines its exit code.
The fix is easy: you mean
if array_contains exclude "$1"; then
though I would probably refactor the code to reduce its complexity more significantly.
As pointed out numerous times the if statement runs a command and examines its exit code.
bash$ flase () { echo "flase."; return 1; }
bash$ ture () { echo "ture."; return 0; }
bash$ if ture; then echo It is true.; else echo It is not.; fi
ture.
It is true.
bash$ if flase; then echo It is not false.; else echo It is false.; fi
flase.
It is false.
Dude, so sorry about the absolute noob question.
What is the problem with the following code?
I like to have a simple script which says MEH! when there is no input arg, otherwise prints it.
#!/bin/bash
if [${#$1}==0] then
echo "MEH!";
else
echo $1;
fi
OS says the line 4 has a error (unexpected token else at line 4).
So sorry dude.
Thanks in advance.
You probably wanted to use:
#!/bin/bash
if [ ${#1} -eq 0 ]; then
echo "MEH!";
else
echo $1
fi
Problems in your current if [${#$1}==0] then condition:
spaces around [ and ] are needed. For further information, you can check the excellent answer by Charles Bailey in Why equal to operator does not work if its not surrounded by space?.
== is used on string comparison. In this case you want integer comparison, that is -eq. See Bash Conditional Expressions in the manual for a full list of them.
In general, if you want to check if your script is receiving at least a parameter, you'd better do:
if [ $# -ge 1 ]; then
echo "$# parameters given"
else
echo "no parameters given"
fi
or also, as commented by Charles Duffy:
if [ -z "$1" ] # true if the variable $1 has length 0
Last and not least: check the comments below, as good information was provided.
I want in a bash script (Linux) to check, if two files are identical.
I use the following code:
#!/bin/bash
…
…
differ=$(diff $FILENAME.out_ok $FILENAME.out)
echo "******************"
echo $differ
echo "******************"
if [ $differ=="" ]
then
echo "pass"
else
echo "Error ! different output"
echo $differ
fi
The problem:
the diff command return white space and break the if command
output
******************
82c82 < ---------------------- --- > ---------------------
******************
./test.sh: line 32: [: too many arguments
Error ! different output
The correct tool for checking whether two files are identical is cmp.
if cmp -s $FILENAME.out_ok $FILENAME.out
then : They are the same
else : They are different
fi
Or, in this context:
if cmp -s $FILENAME.out_ok $FILENAME.out
then
echo "pass"
else
echo "Error ! different output"
diff $FILENAME.out_ok $FILENAME.out
fi
If you want to use the diff program, then double quote your variable (and use spaces around the arguments to the [ command):
if [ -z "$differ" ]
then
echo "pass"
else
echo "Error ! different output"
echo "$differ"
fi
Note that you need to double quote the variable when you echo it to ensure that newlines etc are preserved in the output; if you don't, everything is mushed onto a single line.
Or use the [[ test:
if [[ "$differ" == "" ]]
then
echo "pass"
else
echo "Error ! different output"
echo "$differ"
fi
Here, the quotes are not strictly necessary around the variable in the condition, but old school shell scripters like me would put them there automatically and harmlessly. Roughly, if the variable might contain spaces and the spaces matter, it should be double quoted. I don't see a need to learn a special case for the [[ command when it works fine with double quotes too.
Instead of:
if [ $differ=="" ]
Use:
if [[ $differ == "" ]]
Better to use modern [[ and ]] instead of an external program /bin/[
Also use diff -b to compare 2 files while ignoring white spaces
#anubhava answer is correct,
you can also use
if [ "$differ" == "" ]
I'm trying to write a shell script which will compare two files, and if there are no differences between then, it will indicate that there was a success, and if there are differences, it will indicate that there was a failure, and print the results. Here's what I have so far:
result = $(diff -u file1 file2)
if [ $result = "" ]; then
echo It works!
else
echo It does not work
echo $result
fi
Anybody know what I'm doing wrong???
result=$(diff -u file1 file2)
if [ $? -eq 0 ]; then
echo "It works!"
else
echo "It does not work"
echo "$result"
fi
Suggestions:
No spaces around "=" in the variable assignment for results
Use $? status variable after running diff instead of the string length of $result.
I'm in the habit of using backticks for command substitution instead of $(), but #Dennis Williamson cites some good reasons to use the latter after all. Thanks Dennis!
Applied quotes per suggestions in comments.
Changed "=" to "-eq" for numeric test.
First, you should wrap strings being compared with quotes.
Second, "!" cannot be use it has another meaning. You can wrap it with single quotes.
So your program will be.
result=$(diff -u file1 file2)
if [ "$result" == "" ]; then
echo 'It works!'
else
echo It does not work
echo "$result"
fi
Enjoy.
Since you need results when you fail, why not simply use 'diff -u file1 file2' in your script? You may not even need a script then. If diff succeeds, nothing will happen, else the diff will be printed.
bash string equivalence is "==".
-n is non-zero string, -z is zero length string, wrapping in quotes because the command will complain if the output of diff is longer than a single string with "too many arguments".
so
if [ -n "$(diff $1 $2)" ]; then
echo "Different"
fi
or
if [ -z "$(diff $1 $2)" ]; then
echo "Same"
fi