Check a c file output in Linux - linux

I have 2 files .c which only contain a printf("x")
I am in bash script and i want to check if the values in the printf are for project1.c =20 and for project 2 =10,and then make some changes depending on the values.
How am i supposed to make the comparison in the if command?
This is what i have tried to do,not sure if it is right way.
for d in $1/*/*
do
gcc project1 project1.c
if[ ./project1 = 20 ];then
$project1 =30
else
$project1 =0
fi
gcc project2 project2.c
if[ ./project2 =10 ];then
$project2 = 70
else
$project2 = 0
fi
sum=$project1 + $project2
echo "project1 : $project1 project2: $project2 total grade: $sum" >> grades.txt
done
fi

Your invocation of gcc is wrong. You have to specify the output file:
gcc -o project1 project1.c
Next, in shell, variable substitution is a different process than assignment. So, you can't write $var=foo. The correct syntax is var=foo.
Then, space is a special character (it is used to separate arguments). So var=foo is not the same than var = foo. So, the correct syntax is:
project1=30
Next, in shell, the pattern $(command) is replaced by the result of command. So. I have to do:
if [ $(./project2) == 10 ]; then
Finally, you can do arithmetic using $((calculus)). So, you have to write:
sum=$(($project1 + $project2))

Related

Changing global var inside function doesnt mutate global variable [duplicate]

I'm working with this:
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
I have a script like below:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
test1
echo "$e"
Which returns:
hello
4
But if I assign the result of the function to a variable, the global variable e is not modified:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
ret=$(test1)
echo "$ret"
echo "$e"
Returns:
hello
2
I've heard of the use of eval in this case, so I did this in test1:
eval 'e=4'
But the same result.
Could you explain me why it is not modified? How could I save the echo of the test1 function in ret and modify the global variable too?
When you use a command substitution (i.e., the $(...) construct), you are creating a subshell. Subshells inherit variables from their parent shells, but this only works one way: A subshell cannot modify the environment of its parent shell.
Your variable e is set within a subshell, but not the parent shell. There are two ways to pass values from a subshell to its parent. First, you can output something to stdout, then capture it with a command substitution:
myfunc() {
echo "Hello"
}
var="$(myfunc)"
echo "$var"
The above outputs:
Hello
For a numerical value in the range of 0 through 255, you can use return to pass the number as the exit status:
mysecondfunc() {
echo "Hello"
return 4
}
var="$(mysecondfunc)"
num_var=$?
echo "$var - num is $num_var"
This outputs:
Hello - num is 4
This needs bash 4.1 if you use {fd} or local -n.
The rest should work in bash 3.x I hope. I am not completely sure due to printf %q - this might be a bash 4 feature.
Summary
Your example can be modified as follows to archive the desired effect:
# Add following 4 lines:
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$#" "$?"; }
_capture() { { out="$("${#:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$#")"; }
e=2
# Add following line, called "Annotation"
function test1_() { passback e; }
function test1() {
e=4
echo "hello"
}
# Change following line to:
capture ret test1
echo "$ret"
echo "$e"
prints as desired:
hello
4
Note that this solution:
Works for e=1000, too.
Preserves $? if you need $?
The only bad sideffects are:
It needs a modern bash.
It forks quite more often.
It needs the annotation (named after your function, with an added _)
It sacrifices file descriptor 3.
You can change it to another FD if you need that.
In _capture just replace all occurances of 3 with another (higher) number.
The following (which is quite long, sorry for that) hopefully explains, how to adpot this recipe to other scripts, too.
The problem
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
d1=$(d)
d2=$(d)
d3=$(d)
d4=$(d)
echo $x $d1 $d2 $d3 $d4
outputs
0 20171129-123521 20171129-123521 20171129-123521 20171129-123521
while the wanted output is
4 20171129-123521 20171129-123521 20171129-123521 20171129-123521
The cause of the problem
Shell variables (or generally speaking, the environment) is passed from parental processes to child processes, but not vice versa.
If you do output capturing, this usually is run in a subshell, so passing back variables is difficult.
Some even tell you, that it is impossible to fix. This is wrong, but it is a long known difficult to solve problem.
There are several ways on how to solve it best, this depends on your needs.
Here is a step by step guide on how to do it.
Passing back variables into the parental shell
There is a way to pass back variables to a parental shell. However this is a dangerous path, because this uses eval. If done improperly, you risk many evil things. But if done properly, this is perfectly safe, provided that there is no bug in bash.
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
d() { let x++; d=$(date +%Y%m%d-%H%M%S); _passback x d; }
x=0
eval `d`
d1=$d
eval `d`
d2=$d
eval `d`
d3=$d
eval `d`
d4=$d
echo $x $d1 $d2 $d3 $d4
prints
4 20171129-124945 20171129-124945 20171129-124945 20171129-124945
Note that this works for dangerous things, too:
danger() { danger="$*"; passback danger; }
eval `danger '; /bin/echo *'`
echo "$danger"
prints
; /bin/echo *
This is due to printf '%q', which quotes everything such, that you can re-use it in a shell context safely.
But this is a pain in the a..
This does not only look ugly, it also is much to type, so it is error prone. Just one single mistake and you are doomed, right?
Well, we are at shell level, so you can improve it. Just think about an interface you want to see, and then you can implement it.
Augment, how the shell processes things
Let's go a step back and think about some API which allows us to easily express, what we want to do.
Well, what do we want do do with the d() function?
We want to capture the output into a variable.
OK, then let's implement an API for exactly this:
# This needs a modern bash 4.3 (see "help declare" if "-n" is present,
# we get rid of it below anyway).
: capture VARIABLE command args..
capture()
{
local -n output="$1"
shift
output="$("$#")"
}
Now, instead of writing
d1=$(d)
we can write
capture d1 d
Well, this looks like we haven't changed much, as, again, the variables are not passed back from d into the parent shell, and we need to type a bit more.
However now we can throw the full power of the shell at it, as it is nicely wrapped in a function.
Think about an easy to reuse interface
A second thing is, that we want to be DRY (Don't Repeat Yourself).
So we definitively do not want to type something like
x=0
capture1 x d1 d
capture1 x d2 d
capture1 x d3 d
capture1 x d4 d
echo $x $d1 $d2 $d3 $d4
The x here is not only redundant, it's error prone to always repeate in the correct context. What if you use it 1000 times in a script and then add a variable? You definitively do not want to alter all the 1000 locations where a call to d is involved.
So leave the x away, so we can write:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
d() { let x++; output=$(date +%Y%m%d-%H%M%S); _passback output x; }
xcapture() { local -n output="$1"; eval "$("${#:2}")"; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
outputs
4 20171129-132414 20171129-132414 20171129-132414 20171129-132414
This already looks very good. (But there still is the local -n which does not work in oder common bash 3.x)
Avoid changing d()
The last solution has some big flaws:
d() needs to be altered
It needs to use some internal details of xcapture to pass the output.
Note that this shadows (burns) one variable named output,
so we can never pass this one back.
It needs to cooperate with _passback
Can we get rid of this, too?
Of course, we can! We are in a shell, so there is everything we need to get this done.
If you look a bit closer to the call to eval you can see, that we have 100% control at this location. "Inside" the eval we are in a subshell,
so we can do everything we want without fear of doing something bad to the parental shell.
Yeah, nice, so let's add another wrapper, now directly inside the eval:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
# !DO NOT USE!
_xcapture() { "${#:2}" > >(printf "%q=%q;" "$1" "$(cat)"); _passback x; } # !DO NOT USE!
# !DO NOT USE!
xcapture() { eval "$(_xcapture "$#")"; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
prints
4 20171129-132414 20171129-132414 20171129-132414 20171129-132414
However, this, again, has some major drawback:
The !DO NOT USE! markers are there,
because there is a very bad race condition in this,
which you cannot see easily:
The >(printf ..) is a background job. So it might still
execute while the _passback x is running.
You can see this yourself if you add a sleep 1; before printf or _passback.
_xcapture a d; echo then outputs x or a first, respectively.
The _passback x should not be part of _xcapture,
because this makes it difficult to reuse that recipe.
Also we have some unneded fork here (the $(cat)),
but as this solution is !DO NOT USE! I took the shortest route.
However, this shows, that we can do it, without modification to d() (and without local -n)!
Please note that we not neccessarily need _xcapture at all,
as we could have written everyting right in the eval.
However doing this usually isn't very readable.
And if you come back to your script in a few years,
you probably want to be able to read it again without much trouble.
Fix the race
Now let's fix the race condition.
The trick could be to wait until printf has closed it's STDOUT, and then output x.
There are many ways to archive this:
You cannot use shell pipes, because pipes run in different processes.
One can use temporary files,
or something like a lock file or a fifo. This allows to wait for the lock or fifo,
or different channels, to output the information, and then assemble the output in some correct sequence.
Following the last path could look like (note that it does the printf last because this works better here):
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_xcapture() { { printf "%q=%q;" "$1" "$("${#:2}" 3<&-; _passback x >&3)"; } 3>&1; }
xcapture() { eval "$(_xcapture "$#")"; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
outputs
4 20171129-144845 20171129-144845 20171129-144845 20171129-144845
Why is this correct?
_passback x directly talks to STDOUT.
However, as STDOUT needs to be captured in the inner command,
we first "save" it into FD3 (you can use others, of course) with '3>&1'
and then reuse it with >&3.
The $("${#:2}" 3<&-; _passback x >&3) finishes after the _passback,
when the subshell closes STDOUT.
So the printf cannot happen before the _passback,
regardless how long _passback takes.
Note that the printf command is not executed before the complete
commandline is assembled, so we cannot see artefacts from printf,
independently how printf is implemented.
Hence first _passback executes, then the printf.
This resolves the race, sacrificing one fixed file descriptor 3.
You can, of course, choose another file descriptor in the case,
that FD3 is not free in your shellscript.
Please also note the 3<&- which protects FD3 to be passed to the function.
Make it more generic
_capture contains parts, which belong to d(), which is bad,
from a reusability perspective. How to solve this?
Well, do it the desparate way by introducing one more thing,
an additional function, which must return the right things,
which is named after the original function with _ attached.
This function is called after the real function, and can augment things.
This way, this can be read as some annotation, so it is very readable:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_capture() { { printf "%q=%q;" "$1" "$("${#:2}" 3<&-; "$2_" >&3)"; } 3>&1; }
capture() { eval "$(_capture "$#")"; }
d_() { _passback x; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
capture d1 d
capture d2 d
capture d3 d
capture d4 d
echo $x $d1 $d2 $d3 $d4
still prints
4 20171129-151954 20171129-151954 20171129-151954 20171129-151954
Allow access to the return-code
There is only on bit missing:
v=$(fn) sets $? to what fn returned. So you probably want this, too.
It needs some bigger tweaking, though:
# This is all the interface you need.
# Remember, that this burns FD=3!
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$#" "$?"; }
_capture() { { out="$("${#:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$#")"; }
# Here is your function, annotated with which sideffects it has.
fails_() { passback x y; }
fails() { x=$1; y=69; echo FAIL; return 23; }
# And now the code which uses it all
x=0
y=0
capture wtf fails 42
echo $? $x $y $wtf
prints
23 42 69 FAIL
There is still a lot room for improvement
_passback() can be elmininated with passback() { set -- "$#" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
_capture() can be eliminated with capture() { eval "$({ out="$("${#:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }
The solution pollutes a file descriptor (here 3) by using it internally.
You need to keep that in mind if you happen to pass FDs.
Note thatbash 4.1 and above has {fd} to use some unused FD.
(Perhaps I will add a solution here when I come around.)
Note that this is why I use to put it in separate functions like _capture, because stuffing this all into one line is possible, but makes it increasingly harder to read and understand
Perhaps you want to capture STDERR of the called function, too.
Or you want to even pass in and out more than one filedescriptor
from and to variables.
I have no solution yet, however here is a way to catch more than one FD, so we can probably pass back the variables this way, too.
Also do not forget:
This must call a shell function, not an external command.
There is no easy way to pass environment variables out of external commands.
(With LD_PRELOAD= it should be possible, though!)
But this then is something completely different.
Last words
This is not the only possible solution. It is one example to a solution.
As always you have many ways to express things in the shell.
So feel free to improve and find something better.
The solution presented here is quite far from being perfect:
It was nearly not tested at all, so please forgive typos.
There is a lot of room for improvement, see above.
It uses many features from modern bash, so probably is hard to port to other shells.
And there might be some quirks I haven't thought about.
However I think it is quite easy to use:
Add just 4 lines of "library".
Add just 1 line of "annotation" for your shell function.
Sacrifices just one file descriptor temporarily.
And each step should be easy to understand even years later.
Maybe you can use a file, write to file inside function, read from file after it. I have changed e to an array. In this example blanks are used as separator when reading back the array.
#!/bin/bash
declare -a e
e[0]="first"
e[1]="secondddd"
function test1 () {
e[2]="third"
e[1]="second"
echo "${e[#]}" > /tmp/tempout
echo hi
}
ret=$(test1)
echo "$ret"
read -r -a e < /tmp/tempout
echo "${e[#]}"
echo "${e[0]}"
echo "${e[1]}"
echo "${e[2]}"
Output:
hi
first second third
first
second
third
What you are doing, you are executing test1
$(test1)
in a sub-shell( child shell ) and Child shells cannot modify anything in parent.
You can find it in bash manual
Please Check: Things results in a subshell here
I had a similar problem when I wanted to remove temporary files I had created automatically. The solution I came up with was not to use command substitution, but rather to pass the name of the variable, that should take the final result, into the function. E.g.
#!/usr/bin/env bash
# array that keeps track of tmp-files
remove_later=()
# function that manages tmp-files
new_tmp_file() {
file=$(mktemp)
remove_later+=( "$file" )
# assign value (safe form of `eval "$1=$file"`)
printf -v "$1" -- "$file"
}
# function to remove all tmp-files
remove_tmp_files() { rm -- "${remove_later[#]}"; }
# define trap to remove all tmp-files upon EXIT
trap remove_tmp_files EXIT
# generate tmp-files
new_tmp_file tmpfile1
new_tmp_file tmpfile2
So, adapting this to the OP, it would be:
#!/usr/bin/env bash
e=2
function test1() {
e=4
printf -v "$1" -- "hello"
}
test1 ret
echo "$ret"
echo "$e"
Works and has no restrictions on the "return value".
Assuming that local -n is available, the following script lets the function test1 modify a global variable:
#!/bin/bash
e=2
function test1() {
local -n var=$1
var=4
echo "hello"
}
test1 e
echo "$e"
Which gives the following output:
hello
4
I'm not sure if this works on your terminal, but I found out that if you don't provide any outputs whatsoever it gets naturally treated as a void function, and can make global variable changes.
Here's the code I used:
let ran1=$(( (1<<63)-1)/3 ))
let ran2=$(( (1<<63)-1)/5 ))
let c=0
function randomize {
c=$(( ran1+ran2 ))
ran2=$ran1
ran1=$c
c=$(( c > 0 ))
}
It's a simple randomizer for games that effectively modifies the needed variables.
It's because command substitution is performed in a subshell, so while the subshell inherits the variables, changes to them are lost when the subshell ends.
Reference:
Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment
A solution to this problem, without having to introduce complex functions and heavily modify the original one, is to store the value in a temporary file and read / write it when needed.
This approach helped me greatly when I had to mock a bash function called multiple times in a bats test case.
For example, you could have:
# Usage read_value path_to_tmp_file
function read_value {
cat "${1}"
}
# Usage: set_value path_to_tmp_file the_value
function set_value {
echo "${2}" > "${1}"
}
#----
# Original code:
function test1() {
e=4
set_value "${tmp_file}" "${e}"
echo "hello"
}
# Create the temp file
# Note that tmp_file is available in test1 as well
tmp_file=$(mktemp)
# Your logic
e=2
# Store the value
set_value "${tmp_file}" "${e}"
# Run test1
test1
# Read the value modified by test1
e=$(read_value "${tmp_file}")
echo "$e"
The drawback is that you might need multiple temp files for different variables. And also you might need to issue a sync command to persist the contents on the disk between one write and read operations.
You can always use an alias:
alias next='printf "blah_%02d" $count;count=$((count+1))'

linux command rename dates (YYYY.MMDD) to numbers(001,002,...,066,067) sequentially

I have renamed many files by using 'rename'.
However, I find a problem with conversion dates to numbers.
The file name is 2021.0801, 2021.0802, .. etc. (Year.Month&date)
I need to change Month&date parts to numbers of 001, 002, etc.
So I need to rename
2021.0801
2021.0802
...
2021.0929
2021.0930
to
2021.001
2021.002
...
2021.0**
2021.0**
I saw I can do it when I use rename or #, ? but I could not see the specific way to solve this.
Could you please let me know the way to rename these?
p.s. I tried num=001; for i in {0801..0930}; do rename $i $num *; (($num++)); done but it showed
2021.001
2021.001001
2021.001001001001
...
Additionally, ls 2021.* shows only the files that I want to change.
Your script contains a few errors. I suggest to use https://www.shellcheck.net/ to check your scripts.
After fixing the errors, the (unfinished) result is
#!/bin/bash
num=001; for i in {0801..0930}; do rename "$i" "$num" ./*; ((num++)); done
This has 3 remaining problems.
The range {0801..0930} includes the numbers 0831, 0832, ... 0899, 0900. This can be fixed by using two ranges {0801..0831} {0901..0930}
The increment operation ((num++)) does not preserve the leading zeros. This can be fixed by conditionally adding 1 or 2 zeros.
You call rename for every combination which will check all files and probably rename only one. As you know the exact file names you can replace this with a mv command.
The final version is
num=1; for i in {0801..0831} {0901..0930}; do
if [[ num -lt 10 ]] ; then
new="00$num";
elif [[ num -lt 100 ]] ; then
new="0$num";
else
new="$num"; # this case is not necessary here
fi;
mv "2021.$i" "2021.$new";
((num++));
done
The script handles leading zeros for values of 1, 2 or 3 digits which is not needed here as all numbers are less than 100. For this case, it can be simplified as
num=1; for i in {0801..0831} {0901..0930}; do
if [[ num -lt 10 ]] ; then
new="00$num";
else
new="0$num";
fi;
mv "2021.$i" "2021.$new";
((num++));
done
The script will execute these commands:
mv 2021.0801 2021.001
mv 2021.0802 2021.002
...
mv 2021.0830 2021.030
mv 2021.0831 2021.031
mv 2021.0901 2021.032
mv 2021.0902 2021.033
...
mv 2021.0929 2021.060
mv 2021.0930 2021.061
You don't need the for loop. This single command will do it all :
rename -n 'BEGIN{our $num=1}{our $num;s/\d+$/sprintf("%03d", $num)/e; $num += 1}' 2021.*
Remove -n once the resulting renaming looks good.

Iterate variables in a file to check for a particular value in bash

Below is my requirement. I have a text file that has following content
File name - abc.txt
Content -
apple=0
mango=1
strawberry=10
I need to kick off the subsequent process only if any of the above stated variable has non zero values.
In this case, As two variables have values 1 and 10 respectively, I need to update an indicator - SKIP INDICATOR=N
If all variables have 0 as value, I need to update SKIP INDICATOR=Y
How to achieve this functionality in Linux. Kindly advise.
with very simple greps :
if [ $(grep '=' your_file | grep -v '=0') ]
then
echo "non zero values detected"
SKIP_INDICATOR=N
else
echo "all are zeroes"
SKIP_INDICATOR=Y
fi
Just note that this is a quick and dirty solution and it would NOT work properly if you have for example a=01 or a= 0 (eg with space)
Try:
grep -q '=0*[1-9]' textfile && skip_indicator=N || skip_indicator=Y
=0*[1-9] matches an '=' character followed by zero or more '0' characters followed by a digit in the range 1 to 9.
See Correct Bash and shell script variable capitalization for an explanation of why I changed SKIP_INDICATOR to skip_indicator.
#!/bin/bash
flag=`awk -F'=' '$NF!="0"{print;exit}' input`
if [ ! -z $flag ] ; then
SKIP_INDICATOR=N
echo "some variable value is different from 0. do something"
else
SKIP_INDICATOR=Y
echo "all variables have 0 as value. do another thing."
fi
exit 0

Compare values in Linux

I'm using .conf which contain keys and values.
Some keys contains numbers like
deployment.conf
EAR_COUNT=2
EAR_1=xxx.ear
EAR_2=yyy.ear
When I try to retrieve that value using particular key and compare with integer value i.e. natural number.
But Whatever I retrieved values from .conf ,it is should be String datatype.
How should I compare both value in Linux Bash script.
Simply : How should I compare two values in Linux.?
Ex :
. ./deployment.conf
count=$EAR_COUNT;
echo "count : $count";
if [ $count -gt 0 ]; then
echo "Test"
fi
I'm getting following error :
count : 2
: integer expression expected30: [: 2
They're all strings in bash, notwithstanding your ability to do typeset-type things to flag them differently.
If you want to do numeric comparisons, just use -eq (or its brethren like -gt, -le) rather than ==, != and so on:
if [[ $num -eq 42 ]] ; then
echo Found the answer
fi
The full range of comparison operators can be found in the bash manpage, under CONDITIONAL EXPRESSIONS.
If you have something that you think should be a number and it's not working, I'll warrant it's not a number. Do something like:
echo "[$count]"
to make sure it doesn't have a newline at the end or, better yet, get a hex dump of it in case it holds strange characters, like Windows line endings:
echo -n $count | od -xcb
The fact that you're seeing:
: integer expression expected30: [: 2
with the : back at the start of the line, rather than the more usual:
-bash: [: XX: integer expression expected
tends to indicate the presence of a carriage return in there, which might be from deployment.conf having those Windows line endings (\r\n rather than the UNIXy \n).
The hex dump should make that obvious, at which point you need to go and clean up your configuration file.
Ref : http://linux.die.net/man/1/bash
-eq, -ne, -lt, -le, -gt, or -ge
These are arithmetic binary operators in bash scripting.
I have checked your code,
deployment.conf
# CONF FILE
EAR_COUNT=5
testArithmetic.sh
#!/bin/bash
. ./deployment.conf
count=$EAR_COUNT;
echo "count : $count";
if [ $count -gt 0 ]; then
echo "Test"
fi
running the above script evaluates to numeric comparison for fine. Share us your conf file contents, if you are facing any issues. If you are including the conf file in your script file, note the conf file must have valid BASH assignments, which means, there should be no space before and after '=' sign.
Also, you have mentioned WAR_COUNT=3 in conf part and used 'count=$EAR_COUNT;' in script part. Please check this too.
Most likely you have some non-integer character like \r in your EAR_COUNT variable. Strip all non-digits while assigning to count like this:
count=${EAR_COUNT//[^[:digit:]]/}
echo "count : $count";
if [[ $count -gt 0 ]]; then
echo "Test"
fi

Bash prompt with the last exit code

I've been trying to customize my Bash prompt so that it will look like
[feralin#localhost ~]$ _
with colors. I managed to get constant colors (the same colors every time I see the prompt), but I want the username ('feralin') to appear red, instead of green, if the last command had a nonzero exit status. I came up with:
\e[1;33m[$(if [[ $? == 0 ]]; then echo "\e[0;31m"; else echo "\e[0;32m"; fi)\u\e[m#\e[1;34m\h \e[0;35m\W\e[1;33m]$ \e[m
However, from my observations, the $(if ...; fi) seems to be evaluated once, when the .bashrc is run, and the result is substituted forever after. This makes the name always green, even if the last exit code is nonzero (as in, echo $?). Is this what is happening? Or is it simply something else wrong with my prompt? Long question short, how do I get my prompt to use the last exit code?
As you are starting to border on a complex PS1, you might consider using PROMPT_COMMAND. With this, you set it to a function, and it will be run after each command to generate the prompt.
You could try the following in your ~/.bashrc file:
PROMPT_COMMAND=__prompt_command # Function to generate PS1 after CMDs
__prompt_command() {
local EXIT="$?" # This needs to be first
PS1=""
local RCol='\[\e[0m\]'
local Red='\[\e[0;31m\]'
local Gre='\[\e[0;32m\]'
local BYel='\[\e[1;33m\]'
local BBlu='\[\e[1;34m\]'
local Pur='\[\e[0;35m\]'
if [ $EXIT != 0 ]; then
PS1+="${Red}\u${RCol}" # Add red if exit code non 0
else
PS1+="${Gre}\u${RCol}"
fi
PS1+="${RCol}#${BBlu}\h ${Pur}\W${BYel}$ ${RCol}"
}
This should do what it sounds like you want. Take a look a my bashrc's sub file if you want to see all the things I do with my __prompt_command function.
If you don't want to use the prompt command there are two things you need to take into account:
getting the value of $? before anything else. Otherwise it'll be overridden.
escaping all the $'s in the PS1 (so it's not evaluated when you assign it)
Working example using a variable
PS1="\$(VALU="\$?" ; echo \$VALU ; date ; if [ \$VALU == 0 ]; then echo zero; else echo nonzero; fi) "
Working example without a variable
Here the if needs to be the first thing, before any command that would override the $?.
PS1="\$(if [ \$? == 0 ]; then echo zero; else echo nonzero; fi) "
Notice how the \$() is escaped so it's not executed right away, but each time PS1 is used. Also all the uses of \$?.
Compact solution:
PS1='... $(code=${?##0};echo ${code:+[error: ${code}]})'
This approach does not require PROMPT_COMMAND (apparently this can be slower sometimes) and prints [error: <code>] if the exit code is non-zero, and nothing if it's zero:
... > false
... [error: 1]> true
... >
Change the [error: ${code}] part depending on your liking, with ${code} being the non-zero code to print.
Note the use of ' to ensure the inline $() shell gets executed when PS1 is evaluated later, not when the shell is started.
As bonus, you can make it colorful in red by adding \e[01;31m in front and \e[00m after to reset:
PS1='... \e[01;31m$(code=${?##0};echo ${code:+[error: ${code}]})\e[00m'
--
How it works:
it uses bash parameter substitution
first, the ${?##0} will read the exit code $? of the previous command
the ## will remove any 0 pattern from the beginning, effectively making a 0 result an empty var (thanks #blaskovicz for the trick)
we assign this to a temporary code variable as we need to do another substitution, and they can't be nested
the ${code:+REPLACEMENT} will print the REPLACEMENT part only if the variable code is set (non-empty)
this way we can add some text and brackets around it, and reference the variable again inline: [error: ${code}]
I wanted to keep default Debian colors, print the exact code, and only print it on failure:
# Show exit status on failure.
PROMPT_COMMAND=__prompt_command
__prompt_command() {
local curr_exit="$?"
local BRed='\[\e[0;91m\]'
local RCol='\[\e[0m\]'
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u#\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
if [ "$curr_exit" != 0 ]; then
PS1="[${BRed}$curr_exit${RCol}]$PS1"
fi
}
The following provides a leading green check mark when the exit code is zero and a red cross in all other cases. The remainder is a standard colorized prompt. The printf statements can be modified to present the two states that were originally requested.
PS1='$(if [ $? -eq 0 ]; then printf "\033[01;32m""\xE2\x9C\x93"; else printf "\033[01;31m""\xE2\x9C\x95"; fi) \[\e[00;32m\]\u#\h\[\e[00;30m\]:\[\e[01;33m\]\w\[\e[01;37m\]\$ '
Why didn't I think about that myself? I found this very interesting and added this feature to my 'info-bar' project. Eyes will turn red if the last command failed.
#!/bin/bash
eyes=(O o ∘ ◦ ⍤ ⍥) en=${#eyes[#]} mouth='_'
face () { # gen random face
[[ $error -gt 0 ]] && ecolor=$RED || ecolor=$YLW
if [[ $1 ]]; then printf "${eyes[$[RANDOM%en]]}$mouth${eyes[$[RANDOM%en]]}"
else printf "$ecolor${eyes[$[RANDOM%en]]}$YLW$mouth$ecolor${eyes[$[RANDOM%en]]}$DEF"
fi
}
info () { error=$?
[[ -d .git ]] && { # If in git project folder add git status to info bar output
git_clr=('GIT' $(git -c color.ui=always status -sb)) # Colored output 4 info
git_tst=('GIT' $(git status -sb)) # Simple output 4 test
}
printf -v line "%${COLUMNS}s" # Set border length
date=$(printf "%(%a %d %b %T)T") # Date & time 4 test
test=" O_o $PWD ${git_tst[*]} $date o_O " # Test string
step=$[$COLUMNS-${#test}]; [[ $step -lt 0 ]] && step=0 # Count spaces
line="$GRN${line// /-}$DEF\n" # Create lines
home="$BLD$BLU$PWD$DEF" # Home dir info
date="$DIM$date$DEF" # Colored date & time
#------+-----+-------+--------+-------------+-----+-------+--------+
# Line | O_o |homedir| Spaces | Git status | Date| o_O | Line |
#------+-----+-------+--------+-------------+-----+-------+--------+
printf "$line $(face) $home %${step}s ${git_clr[*]} $date $(face) \n$line" # Final info string
}
PS1='${debian_chroot:+($debian_chroot)}\n$(info)\n$ '
case "$TERM" in xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)} $(face 1) \w\a\]$PS1";;
esac
Improved demure answer:
I think this is important because the exit status is not always 0 or 1.
if [ $EXIT != 0 ]; then
PS1+="${Red}${EXIT}:\u${RCol}" # Add red if exit code != 0
else
PS1+="${Gre}${EXIT}:\u${RCol}" # Also displays exit status
fi
To preserve the original prompt format (not just colors),
you could append following to the end of file ~/.bashrc:
PS1_ORIG=$PS1 # original primary prompt value
PROMPT_COMMAND=__update_prompt # Function to be re-evaluated after each command is executed
__update_prompt() {
local PREVIOUS_EXIT_CODE="$?"
if [ $PREVIOUS_EXIT_CODE != 0 ]; then
local RedCol='\[\e[0;31m\]'
local ResetCol='\[\e[0m\]'
local replacement="${RedCol}\u${ResetCol}"
# Replace username color
PS1=${PS1_ORIG//]\\u/]$replacement}
## Alternative: keep same colors, append exit code
#PS1="$PS1_ORIG[${RedCol}error=$PREVIOUS_EXIT_CODE${ResetCol}]$ "
else
PS1=$PS1_ORIG
fi
}
See also the comment about the alternative approach that preserves username color and just appends an error code in red to the end of the original prompt format.
You can achieve a similar result to include a colored (non-zero) exit code in a prompt, without using subshells in the prompt nor prompt_command.
You color the exit code portion of the prompt, while having it only appear when non-zero.
Core 2$ section of the prompt: \\[\\033[0;31;4m\\]\${?#0}\\[\\033[0;33m\\]\$ \\[\\033[0m\\]
Key elements:
return code, if not 0: \${?#0} (specificly "removes prefix of 0")
change color without adding to calculated prompt-width: \\[\\033[0;31m\\]
\\[ - begin block
\\033 - treat as 0-width, in readline calculations for cmdline editing
[0;31;4m - escape code, change color, red fg, underline
\\] - end block
Components:
\\[\\033[0;31;4m\\] - set color 0;31m fg red, underline
\${?#0} - display non-zero status (by removing 0 prefix)
\\[\\033[0;33m\\] - set color 0;33m fg yellow
\$ - $ or # on EUID
\\[\\033[0m\\] - reset color
The full PS1 I use (on one host):
declare -x PS1="\\[\\033[0;35m\\]\\h\\[\\033[1;37m\\] \\[\\033[0;37m\\]\\w \\[\\033[0;33m\\]\\[\\033[0;31;4m\\]\${?#0}\\[\\033[0;33m\\]\$ \\[\\033[0m\\]"
Note: this addresses a natural extension to this question, in a more enduring way then a comment.
Bash
function my_prompt {
local retval=$?
local field1='\u#\h'
local field2='\w'
local field3='$([ $SHLVL -gt 1 ] && echo \ shlvl:$SHLVL)$([ \j -gt 0 ] && echo \ jobs:\j)'"$([ ${retval} -ne 0 ] && echo \ exit:$retval)"
local field4='\$'
PS1=$'\n'"\e[0;35m${field1}\e[m \e[0;34m${field2}\e[m\e[0;31m${field3}\e[m"$'\n'"\[\e[0;36m\]${field4}\[\e[m\] "
}
PROMPT_COMMAND="my_prompt; ${PROMPT_COMMAND}"
Zsh
PROMPT=$'\n''%F{magenta}%n#%m%f %F{blue}%~%f%F{red}%(2L. shlvl:%L.)%(1j. jobs:%j.)%(?.. exit:%?)%f'$'\n''%F{cyan}%(!.#.$)%f '
Images of prompt

Resources