i am trying to write a simple bash script for a custom screensaver, i want simple to go the screen black if in idle, and go back to normal if not.
#!/bin/bash
#sets display gamma very low, for screensaver purposes
idle=false
idle_after=3000 #in milliseconds
while true; do
#if system is idle
if [[ idle_now=$(xprintidle) -gt "$idle_after" && "$idle"=false ]] ;then
echo "1"
`xrandr --output HDMI-0 --brightness 0.01`
idle=true
fi
if [[ idle_now=$(xprintidle) -lt "$idle_after" && "$idle"=true ]] ; then
echo "2"
`xrandr --output HDMI-0 --brightness 1` #set screen back to normal
exit
fi
done
i really don't know why the second if query gets executed first.
i thought the idle variable is initialized "false" at startup
can someone explain this to me ? and if someone has improvements to give on my approach i 'would very appreciate that , thanks
Your test expressions are wrong.
The reason for 2nd if was executed first is that there are no spaces between operands and operators inside test ([[ and ]]).
For better understanding, let's see a possible variable substitution in the first loop assuming that xprintidle returns 10:
[[ idle_now=$(xprintidle) -lt "$idle_after" && "$idle"=true ]]
# becomes
[[ idle_now=10 -lt 3000 && false=true ]]
Now you wanted compare 10 against 3000, but now you're comparing the string "idle_now=10" against 3000. As any string is converted to zero if the characters are not digits, its like you did:
[[ 0 -lt 3000 && false=true ]]
# that becomes
[[ <true> && false=true ]]
Now, the second operand is also one string "false=true" (not a comparison), and any string converted to boolean is false when it's empty. That's not your case, the string has 10 characters, so it's evaluated to true.
[[ <true> && <true> ]]
# that becomes
<true>
Note: I used <true> and <false> just to clarify, they really are internal representation of boolean values.
Fixing it
I imagine idle_now was meant to be a variable that it's never used, so we'll ignore it.
So the if expressions should be:
[[ "$(xprintidle)" -gt "$idle_after" && "$idle" = false ]]
# and
[[ "$(xprintidle)" -lt "$idle_after" && "$idle" = true ]]
Related
How do I compare a variable to a string (and do something if they match)?
Using variables in if statements
if [ "$x" = "valid" ]; then
echo "x has the value 'valid'"
fi
If you want to do something when they don't match, replace = with !=. You can read more about string operations and arithmetic operations in their respective documentation.
Why do we use quotes around $x?
You want the quotes around $x, because if it is empty, your Bash script encounters a syntax error as seen below:
if [ = "valid" ]; then
Non-standard use of == operator
Note that Bash allows == to be used for equality with [, but this is not standard.
Use either the first case wherein the quotes around $x are optional:
if [[ "$x" == "valid" ]]; then
or use the second case:
if [ "$x" = "valid" ]; then
Or, if you don't need an else clause:
[ "$x" == "valid" ] && echo "x has the value 'valid'"
a="abc"
b="def"
# Equality Comparison
if [ "$a" == "$b" ]; then
echo "Strings match"
else
echo "Strings don't match"
fi
# Lexicographic (greater than, less than) comparison.
if [ "$a" \< "$b" ]; then
echo "$a is lexicographically smaller then $b"
elif [ "$a" \> "$b" ]; then
echo "$b is lexicographically smaller than $a"
else
echo "Strings are equal"
fi
Notes:
Spaces between if and [ and ] are important
> and < are redirection operators so escape it with \> and \< respectively for strings.
To compare strings with wildcards, use:
if [[ "$stringA" == *"$stringB"* ]]; then
# Do something here
else
# Do something here
fi
I have to disagree one of the comments in one point:
[ "$x" == "valid" ] && echo "valid" || echo "invalid"
No, that is not a crazy oneliner
It's just it looks like one to, hmm, the uninitiated...
It uses common patterns as a language, in a way;
And after you learned the language.
Actually, it's nice to read
It is a simple logical expression, with one special part: lazy evaluation of the logic operators.
[ "$x" == "valid" ] && echo "valid" || echo "invalid"
Each part is a logical expression; the first may be true or false, the other two are always true.
(
[ "$x" == "valid" ]
&&
echo "valid"
)
||
echo "invalid"
Now, when it is evaluated, the first is checked. If it is false, than the second operand of the logic and && after it is not relevant. The first is not true, so it can not be the first and the second be true, anyway.
Now, in this case is the the first side of the logic or || false, but it could be true if the other side - the third part - is true.
So the third part will be evaluated - mainly writing the message as a side effect. (It has the result 0 for true, which we do not use here)
The other cases are similar, but simpler - and - I promise! are - can be - easy to read!
(I don't have one, but I think being a UNIX veteran with grey beard helps a lot with this.)
The following script reads from a file named "testonthis" line by line and then compares each line with a simple string, a string with special characters and a regular expression. If it doesn't match, then the script will print the line, otherwise not.
Space in Bash is so much important. So the following will work:
[ "$LINE" != "table_name" ]
But the following won't:
["$LINE" != "table_name"]
So please use as is:
cat testonthis | while read LINE
do
if [ "$LINE" != "table_name" ] && [ "$LINE" != "--------------------------------" ] && [[ "$LINE" =~ [^[:space:]] ]] && [[ "$LINE" != SQL* ]]; then
echo $LINE
fi
done
You can also use use case/esac:
case "$string" in
"$pattern" ) echo "found";;
esac
Bash 4+ examples. Note: not using quotes will cause issues when words contain spaces, etc. Always quote in Bash, IMO.
Here are some examples in Bash 4+:
Example 1, check for 'yes' in string (case insensitive):
if [[ "${str,,}" == *"yes"* ]] ;then
Example 2, check for 'yes' in string (case insensitive):
if [[ "$(echo "$str" | tr '[:upper:]' '[:lower:]')" == *"yes"* ]] ;then
Example 3, check for 'yes' in string (case sensitive):
if [[ "${str}" == *"yes"* ]] ;then
Example 4, check for 'yes' in string (case sensitive):
if [[ "${str}" =~ "yes" ]] ;then
Example 5, exact match (case sensitive):
if [[ "${str}" == "yes" ]] ;then
Example 6, exact match (case insensitive):
if [[ "${str,,}" == "yes" ]] ;then
Example 7, exact match:
if [ "$a" = "$b" ] ;then
Enjoy.
I would probably use regexp matches if the input has only a few valid entries. E.g. only the "start" and "stop" are valid actions.
if [[ "${ACTION,,}" =~ ^(start|stop)$ ]]; then
echo "valid action"
fi
Note that I lowercase the variable $ACTION by using the double comma's. Also note that this won't work on too aged bash versions out there.
I did it in this way that is compatible with Bash and Dash (sh):
testOutput="my test"
pattern="my"
case $testOutput in (*"$pattern"*)
echo "if there is a match"
exit 1
;;
(*)
! echo there is no coincidence!
;;esac
I was struggling with the same situation for a while, here is how I could resolve:
if [ "$var1" == "$var2" ]; then
#dowhateveryouwant
fi
Be careful with the spaces left before and after the comparison sign, otherwise it won't work or it'll give you an unexpected result.
I've spent so much time on using a single equal(=) sign but didn't work. I Hope it can help.
Are you having comparison problems? (like below?)
var="true"
if [[ $var == "true" ]]; then
# It should be working, but it is not...
else
# It is falling here...
fi
Try like the =~ operator (regular expression operator) and it might work:
var="true"
if [[ $var =~ "true" ]];then
# Now it works here!!
else
# No more inequality
fi
Bash regex operator =~ (official reference)
StackOverflow further examples (here)
I read many answers to specific uses of boolean or conditional syntax in bash, but none qualified as the best practice many would need (if you haven't I suggest you read those questions before to proceed with this one: search for bash boolean variables and conditional expressions and read the top five).
The difficulty is bash do not have one but many inconsistent, contradictory and counter-intuitive syntax for this, well reflected in contradictory answers in SE.
Goal: knowing that bash has no boolean types, but conditional statement, operators, status codes and functions, to identify the best syntax for widespread use of logical calculus and execution conditional control within a bash script. Widespread means usable across conditional control commands and variables and operators.
The best practice should IMO meet all these requirements, in order of highest importance :
concise if, while, for statements and ternary operator ?:
concise assignment and invocation of variables with boolean value
concise boolean calculus operators or, and, not, and parentheses with command return status codes and variables
examples of competing syntax :
statements: if [[ if [ if command if $variable
assignments: t=true t=1 t=$(true) t='' t=0
operators: [ true -a false ] ( true && false ) (( true * false ))
So, what would be the best syntax to use, e.g. for this practical example but elsewhere too:
boolresutl1=if commandA or ( $numvarB > $numvarC ) then true
boolresult2=commandD
boolresult3=whatever
if boolresult1 or boolresult2 and not boolresult3 then blabla
Best answer would also demonstrate how working alternatives to the proposed best practice would be too verbose.
EDIT: here is a snippet of code that works but still could gain in clarity and conciseness.
The logics goal is to set a variable keepFILE based on a set of time conditions on the file modification date, and file retention policy on Yearly, Quarterly, Monthly, Weekly and Daily. And delete files not meeting that condition.
My key design choice for the script was the value for a true condition, between NULL (satisfying if $var but not [[ $var ]]), or "true" (satisfying [[ $var ]] but not if $var).
# date -d #12345 (seconds since epoch) formats: +%u (7= Sunday) +%d (01= 1st dom) +%m (01=Jan)
unset isY isQ isM isW
# is beg of Week ?
[[ "$(date -d #$filedatesecs +%u)" = 7 ]] && isW=true
# is beg of Month ?
[[ "$(date -d #$filedatesecs +%d)" = 01 ]] && isM=true
# is beg of Quarter ?
[[ $isM && $(date -d #$filedatesecs +%m) =~ ^(01|04|07|10)$ ]] && isQ=true
# is beg of Year ?
[[ $isM && "$(date -d #$filedatesecs +%m)" = 01 ]] && isY=true
unset keepD keepW keepM keepQ keepY keepFILE
[[ $fileagedays -le $maxdaysD ]] && keepD=true
[[ $fileagedays -le $maxdaysW ]] && [[ $isW ]] && keepW=true
[[ $fileagedays -le $maxdaysM ]] && [[ $isM ]] && keepM=true
[[ $fileagedays -le $maxdaysQ ]] && [[ $isQ ]] && keepQ=true
[[ $fileagedays -le $maxdaysY ]] && [[ $isY ]] && keepY=true
[[ $keepD || $keepW || $keepM || $keepQ || $keepY ]] && keepFILE=true
Your pseudocode
# boolresutl1=if commandA or ( $boolvarB > $boolvarC ) then true
# boolresult2=commandD
# boolresult3=whatever
# if boolresult1 or boolresult2 and not boolresult3 then blabla
would be best written with something like
# Assuming `boolVarB` and `$boolvarC` are integer-valued.
if commandA || [ "$boolvarB" -gt "$boolvarC" ] || commandD && ! whatever; then
blabla
fi
Note that || and && have equal precedence; whatever only runs if commandA and [ both fail and commandD succeeds. If you need to group things, use { ... ; }, e.g.,
# Only run commandC if commandA fails and commandB succeeds,
# not when either commandA or commandB succeeds.
if commandA || { commandB && commandC; }; then
If for whatever reason you need to remember exit statuses for later use, immediately save the result of $?:
commandA || [ "$boolvarB" -gt "$boolvarC" ]
result1=$?
commandD
result2=$?
whatever
result3=$?
...
if [ "$result1" -eq 0 ] || [ "$result2" -eq 0 ] || commandD && [ "$result3" -ne 0 ]; then
blablabla
fi
A few comments:
t=$(true) is exactly equivalent to t=, except it takes longer, because true doesn't write anything to standard output.
The -a and -o operators are obsolete and non-standard. Never use them; replace [ x -a y ] with [ x ] && [ y ], and [ x -o y ] with [ x ] || [ y ].
(( true * false )) (for appropriately integer-valued variables) is just unnecessarily confusing.
Hi Guys I am trying to perform a check in my bash script that needs to meet three conditions, I was able to make the first 2 conditions work in the if statement as I wanted, but when I wanted to implement a third check with some arithmetic using the && operator, the script does not even launch.
DOUBLE_CHECK=0
if [[ -z "$avail" && "$WIFI_ID" == "some_str" && 'expr $DOUBLE_CHECK % 2' -eq "0"]]; then
sudo caffeinate xterm -geometry 70x20+0+0 -fa monospace -fs 8 -e './script1.sh' & disown
fi
(($DOUBLE_CHECK++))
The Idea is I want the third check to have a number that increments over time inside my while loop and then checked, whenever it is divisible by 2 it passes the 3rd condition of the if statement
As noted in a separate answer, you could use backticks or preferably $() to expand the expression to the output of the inner command, like this :
if [[ -z "$avail" && $WIFI_ID == "some_str" && $(expr $DOUBLE_CHECK % 2) -eq "0" ]]
Please note I have added a (required) space before the final ]].
Another possibility is to use an arithmetic expression :
if [[ -z $avail && $WIFI_ID == "some_str" ]] && ((DOUBLE_CHECK % 2 == 0))
Please note I have removed harmless but unnecessary quotes : the double-bracketed conditional expression is not a normal command, it is special shell syntax, and it does not perform word splitting. I have left quotes around some_str, because equality/inequality comparisons will perform pattern matching on the right-hand expression if it is not quoted. There is also no $ before the variable name in the arithmetic expression : it works, but is not required inside (()) or $(()).
The expression could also be expressed as :
if [[ -z $avail && $WIFI_ID == "some_str" ]] && ! ((DOUBLE_CHECK % 2))
The reason for this is that (( )) has a return code of 0 if its numerical result is non-zero, and a non-zero return code otherwise.
There are a couple small issues
Your third condition should be evaluated using back-ticks ('`'), instead of quotes. Single-quotes in bash mean string literal. So, 'expr $DOUBLE_CHECK %2' -eq "0" will always be false
The check -eq is used to compare numbers. Your statement is using strings (to compare strings, use the normal == syntax)
Please try,
DOUBLE_CHECK=0
if [[ -z "$avail" ]] && [[ "$WIFI_ID" == "some_str" ]] && [[ `expr $DOUBLE_CHECK % 2` -eq 0 ]]; then
sudo caffeinate xterm -geometry 70x20+0+0 -fa monospace -fs 8 -e './script1.sh' & disown
fi
(($DOUBLE_CHECK++))
You can use [[ .. ]] for string logic (where = and == are equivalent) and (( ... )) for numeric comparison, and do away with expr:
if [[ -z "$avail" && "$WIFI_ID" = "some_str" ]] && (( DOUBLE_CHECK % 2 == 0 )); then
# your logic here
fi
Note that prefixing variables with $ is optional inside (( ... )) and quoting variables is optional inside [[ ... ]] since doesn't do word splitting and globbing there.
I would like to ask you and request for the help with this particular part of the script - I would like to make it more compact as I feel it is too long, or there might me a shorter syntax alternatives...
This part is cut from my script for creation of X amount of files with descending date (1 day jumps, so each file is 1 day older that previous), which is already 3x longer than the whole "implementation" part.
This part's purpose is to maintain, check and correct (appropriately) the arguments defined with the script execution, which looks like
./script.sh X X
whereas script expects two arguments (number indicating amount of files to create and the path to the folder, where it is supposed to create those0:
- if there is just 1 it assumes the user wants to create files in the present folder, therefore it checks, if the one included argument is a number and if it is it will add output from pwd to the path (in this case to not have some sort of "cross-mount" accident with the system variable PATH conveniently named PATHX), so there will be two arguments at the end anyway
- if there are more than 2, it will terminate the script
If there are 2 arguments, it performs additional "tasks:
1. checks if there are just two numbers, if so, script ends
2. checks if there are just two words/letters, if so, script ends
3. if there are, let's say, just two dots as arguments, it will end anyway as there is one if, which always will need one argument to be just number
4. after first steps this will save the arguments to the variables (it is necessary as I have had some problems in the implementation part), this step even coves any possible combination of the positions of the arguments (it does not matter now, if the amount is first or second; same for the path)
5. this is a very last step, which just (for any case) covers the case, the path included has not been inserted with the slash in front (as this is mandatory for bash to recognise it indicates an absolute path) or at the end (important for the touch command in the implementation path as the command looks like "touch -t *TIMESTAMP* "$PATHXfile$i""); together with it it does provide an appropriate action, if the path is defined with ".","..","./","../" - if there would be a full path, not relative one
if [[ $# -eq 2 ]]
then
if [[ $1 == ?(-)+([0-9]) ]] && [[ $2 == ?(-)+([0-9]) ]]
then
echo "Invalid arguments. Did you include a slash at the start of the path (if the file name consists only of the numbers)? Well, try it again. Terminating script..."
exit
elif [[ $1 == ?(-)+([a-z]) ]] && [[ $2 == ?(-)+([a-z]) ]]
then
echo "Only characters in the arguments. Is this some sort of a joke? If you have tried some sick hexadecimal format of number (just A-F), it will not work, mate. Terminating script..."
exit
fi
if [[ $1 == ?(-)+([0-9]) ]]
then
AMOUNT=$1
PATHX=$2
else
AMOUNT=$2
if [[ $AMOUNT != ?(-)+([0-9]) ]]
then
echo "No argument with number/numbers-only as an amount of files to create. Terminating script..."
exit
fi
PATHX=$1
fi
else
if [[ $# -eq 1 ]]
then
if [[ $1 == ?(-)+([0-9]) ]]
then
AMOUNT=$1
PATHX=$(pwd)
else
echo "Please, include the argument with an amount of files to create. Terminating script..."
exit
fi
else
echo "2 Arguments are expected. Terminating script..."
exit
fi
fi
if [[ $PATHX != /* ]]
then
if [[ "$PATHX" == "." ]] || [[ "$PATHX" == ".." ]] || [[ "$PATHX" == "./"* ]] || [[ "$PATHX" == "../"* ]]
then
true
else
PATHX=$(echo "/$PATHX")
fi
fi
if [[ $PATHX != */ ]]
then
PATHX=$(echo "$PATHX/")
fi
Excuse this formatting, but without adding the description to the code sample it is just a mess of text...
Anyway, thank you, Guys, for any input.
It may be reduced to:
[[ $# -gt 2 ]] && { echo "Incorrect number of arguments"; exit 1; }
[[ $1 == ?(-)+([0-9]) ]] || { echo "First argument is not a number"; exit 2; }
[[ $2 == ?(-)+([0-9]) ]] && { echo "Both arguments are numbers"; exit 3; }
[[ $2 == +([a-z]) ]] || { echo "File name must be only letters"; exit 4; }
[[ ! -d $2 ]] && { echo "Path does not exist"; exit 5; }
n=$1
p=${2:-$PWD} # Use the PWD if path is missing.
if [[ $p != */ ]]; then
echo "Missing trailing slash; correcting";
p+=/
fi
if [[ $p != /* ]]; then
echo "Missing leading slash; correcting";
[[ $p == #(.|..|./*|../*) ]] || p=/"$p"
fi
I have seen similar situations but couldn't really figure out how to correctly apply the suggested solutions to my situation.
I have a bash script with the following lines:
LAST=$(ssh root#host ls /backup3/mycomp/partition1/ | tail -1)
#get last backup dir (formatted YYYY-MM-DD/). If none exist then get yesterdays Date
if [[ -z "$LAST" || "$LAST" -eq "$TODAY" ]]
then
log "/backup3/$HOST/$NAME/ does not exist, probably first backup or second backup done today."
LAST="$YESTERDAY"
fi
When I run this, I get the following error:
[[: 2014-11-08: value too great for base (error token is "08")
because I am actually searching for physical directory names, I cant just remove the zero. How would I go about making this work?
Replace:
if [[ -z "$LAST" || "$LAST" -eq "$TODAY" ]]
With:
if [[ -z "$LAST" || "$LAST" == "$TODAY" ]]
Since dates are not valid numbers, you want to do string comparison (==) not numeric comparison (-eq).
Discussion
Observe:
$ [[ "10-1" -eq "9" ]] && echo True
True
$ [[ "6+3" -eq "9" ]] && echo True
True
In the numeric context, signaled -eq, the shell is doing arithmetic on the arguments. This means that, for your date 2014-11-08, the shell was taking 2014, subtracting 11, and then trying to subtract 08. The shell treats any number that begins with a 0 as octal. Since 08 is not a valid octal number, you received an error message.
Comparison of [ and [[
As Jonathan Leffler points out, the implied arithmetic feature of [[ is another subtle difference between the old [ and the newer [[. Observe:
$ [[ "6+3" -eq "9" ]] && echo True
True
$ [ "6+3" -eq "9" ] && echo True
bash: [: 6+3: integer expression expected
The implied arithmetic feature of [[ is not present in [.