How do I redirect warnings in bash? - bash

I am trying to redirect warnings in a shell script. I know to redirect I have to do
command 2>/dev/null
But what I am redirecting is the warning from an if statement. Something like:
if [ "$a" -gt "$b" ] 2>/dev/null
But when I do that my output also goes into null. I mean, if the condition is true then normally I enter into the if-block and a warning msg get displayed "integer expression expected" and all works fine. But when I add the redirection, it just doesnt work. My guess is output is also being redirected. How do I make this work?

To suppress error generated by condition you can do:
[ "$a" -gt "$b" ] 2>/dev/null
However I strongly suggest you find better ways to make sure your variable are of correct type before entering into this condition.
UPDATE
As per comment here is simplistic version comparison script:
v1="$a"
v2="$b"
first=$(echo -e "$v1\n$v2" | sort -nr | head -1)
[[ "$first" = "$v1" ]] && echo "$v1 greater than $v2" || echo "$v1 same or smaller than $v2"

Instead of
if [ "$a" -gt "$b" ] 2>/dev/null
say:
if [ "$a" -gt "$b" 2>/dev/null ]
i.e. include the redirection within the test.
It's better to make use of the mathematical form instead:
(( a > b )) && echo "a is greater than b"

Related

Checking if the string is in proper format in Shell Scripting

I am trying to check if the string is in format in shell script.
below is the code i am trying and the output i want to get.
Format: <datatype>(length,length) | <datatype>(length,length)
I have multiple cases with this scenario, if both datatypes have () then it should show pass, else fail.
Eg. decimal(1,0)|number(11,0) this should pass but int|number(11,0) or decimal(1,0)|int should fail.
Code1:
INPUT='decimal(1,0)|number(11,0)'
sub="[A-Z][a-z]['!##$ %^&*()_+'][0-9][|][A-Z][a-z]['!##$ %^&*()_+'][0-9][|]"
if [ "$INPUT" == "$sub" ]; then
echo "Passed"
else
echo "No"
fi
Code 2:
INPUT='decimal(1,0)|number(11,0)'
sub="decimal"
if [ "$INPUT" == *"("*") |"*"("*") " ]; then
echo "Passed"
else
echo "No"
fi
Any help will be fine. Also note, I am very new to shell scripting.
Reading both values into variables, first removing alpha characters and then checking variables are not empty
result='FAIL'
input='int|number(6,10)'
IFS="|" read val1 val2 <<<"$(tr -d '[:alpha:]' <<<"$input")"
if [ -n "$val1" ] && [ -n "$val2" ]; then
result='PASS'
fi
echo "$result: val1='$val1' val2='$val2'"
Result:
FAIL: val1='' val2='(6,10)'
For input='decimal(8,9)|number(6,10)'
PASS: val1='(8,9)' val2='(6,10)'
That looks like just a simple regex to write.
INPUT='decimal(1,0)|number(11,0)'
if printf "%s" "$INPUT" | grep -qEx '[a-z]+\([0-9]+,[0-9]+\)(\|[a-z]+\([0-9]+,[0-9]+\))*'; then
echo "Passed"
else
echo "No"
fi

How can I pipe output, from a command in an if statement, to a function?

I can't tell if something I'm trying here is simply impossible or if I'm really lacking knowledge in bash's syntax. This is the first script I've written.
I've got a Nextcloud instance that I am backing up daily using a script. I want to log the output of the script as it runs to a log file. This is working fine, but I wanted to see if I could also pipe the Nextcloud occ command's output to the log file too.
I've got an if statement here checking if the file scan fails:
if ! sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all; then
Print "Error: Failed to scan files. Are you in maintenance mode?"
fi
This works fine and I am able to handle the error if the system cannot execute the command. The error string above is sent to this function:
Print()
{
if [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "No" ]; then
echo "$1" | tee -a "$log_file"
elif [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "Yes" ]; then
echo "$1" >> "$log_file"
elif [[ "$logging" -eq 0 ]] && [ "$quiet_mode" = "No" ]; then
echo "$1"
fi
}
How can I make it so the output of the occ command is also piped to the Print() function so it can be logged to the console and log file?
I've tried piping the command after ! using | Print without success.
Any help would be appreciated, cheers!
The Print function doesn't read standard input so there's no point piping data to it. One possible way to do what you want with the current implementation of Print is:
if ! occ_output=$(sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all 2>&1); then
Print "Error: Failed to scan files. Are you in maintenance mode?"
fi
Print "'occ' output: $occ_output"
Since there is only one line in the body of the if statement you could use || instead:
occ_output=$(sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all 2>&1) \
|| Print "Error: Failed to scan files. Are you in maintenance mode?"
Print "'occ' output: $occ_output"
The 2>&1 causes both standard output and error output of occ to be captured to occ_output.
Note that the body of the Print function could be simplified to:
[[ $quiet_mode == No ]] && printf '%s\n' "$1"
(( logging )) && printf '%s\n' "$1" >> "$log_file"
See the accepted, and excellent, answer to Why is printf better than echo? for an explanation of why I replaced echo "$1" with printf '%s\n' "$1".
How's this? A bit unorthodox perhaps.
Print()
{
case $# in
0) cat;;
*) echo "$#";;
esac |
if [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "No" ]; then
tee -a "$log_file"
elif [[ "$logging" -eq 1 ]] && [ "$quiet_mode" = "Yes" ]; then
cat >> "$log_file"
elif [[ "$logging" -eq 0 ]] && [ "$quiet_mode" = "No" ]; then
cat
fi
}
With this, you can either
echo "hello mom" | Print
or
Print "hello mom"
and so your invocation could be refactored to
if ! sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all; then
echo "Error: Failed to scan files. Are you in maintenance mode?"
fi |
Print
The obvious drawback is that piping into a function loses the exit code of any failure earlier in the pipeline.
For a more traditional approach, keep your original Print definition and refactor the calling code to
if output=$(sudo -u "$web_user" "$nextcloud_dir/occ" files:scan --all 2>&1); then
: nothing
else
Print "error $?: $output"
Print "Error: Failed to scan files. Are you in maintenance mode?"
fi
I would imagine that the error message will be printed to standard error, not standard output; hence the addition of 2>&1
I included the error code $? in the error message in case that would be useful.
Sending and receiving end of a pipe must be a process, typically represented by an executable command. An if statement is not a process. You can of course put such a statement into a process. For example,
echo a | (
if true
then
cat
fi )
causes cat to write a to stdout, because the parenthesis put it into a child process.
UPDATE: As was pointed out in a comment, the explicit subprocess is not needed. One can also do a
echo a | if true
then
cat
fi

Parameters work properly when remove their quoting

I am puzzled about the verbose of quoting in the script. Take an example from the instruction I followed:
min_val=1
max_val=100
int=50
if [[ "$int" =~ ^-?[0-9]+$ ]]; then
if [[ "$int" -ge "$min_val" && "$int" -le "$max_val" ]]; then
echo "$int is within $min_val to $max_val."
else
echo "$int is out of range."
fi
else
echo "int is not an integer." >&2
exit 1
fi
Run it and come by:
$ bash test_integer3.sh
50 is within 1 to 100.
When I removed all the quoting in testing:
if [[ $int =~ ^-?[0-9]+$ ]]; then
if [[ $int -ge $min_val && $int -le $max_val ]]; then
echo "$int is within $min_val to $max_val."
else
echo "$int is out of range."
fi
else
echo "int is not an integer." >&2
exit 1
fi
It's still working properly.
$ bash test_integer3.sh
50 is within 1 to 100.
Why should live with the habit of writing redundant quoting?
The real problem comes when you start to use [ command over [[ in your scripts. [[ is bash's improvement to the [ command. It has several enhancements that make it a better choice to write scripts targeting bash.
One such improvement would be that you no longer have to quote variables because [[ handles empty strings and strings with white-space more intuitively. For example consider your script written with [ for the un-quoted case and for discussions sake, one of your variables is empty
#!/usr/bin/env bash
min_val=
max_val=
int=50
if [[ $int =~ ^-?[0-9]+$ ]]; then
if [ $int -ge $min_val -a $int -le $max_val ]; then
echo "$int is within $min_val to $max_val."
else
echo "$int is out of range."
fi
else
echo "int is not an integer." >&2
exit 1
fi
One thing to note is I've re-written the combined conditional using the -a syntax since [ does not support && operator within but could be combined using && as [ $int -ge $min_val ] && [ $int -le $max_val ]
You would see things going bad and seeing errors as below which means that one of the conditionals involving -le is gone wrong on seeing an empty string.
1_script.sh: line 7: [: -a: integer expression expected
50 is out of range.
whereas with same code for undefined variables and replacing the expression to use [[ would gracefully handle the empty strings to produce just an incorrect result as
50 is out of range.
So to sum it up, from the many advantages over using [[, the particular advantage on your case is to handle variables if there could be empty strings in your conditionals.
Quoting is used to to stop the word splitting. In the case above it is not necessary but consider a case like this: you have a directory and having theses files file1.txt, file2.txt, old.txt and file1 old.txt.
If you wish to remove the file file1 old.txt and run the command
rm file1 old.txt
then it will remove the file old.txt instead of what you expected.
In your piece of code you don't need quotes as you discovered. However, using quotes is considered "good practice" because unexpected things can happen without quotes. For example if you run the code with int equal to say "foo bar" you might get some strange results without quotes.
It is like the double and triple equals in JavaScript. You could probably get away with only double equals but some unexpected results might occur.

UNIX multiple if conditions

I have some problem with my code here. This is my code:
#!bin/sh
grep "$1" Rail.txt > test.txt
if [ "$#" -eq 1 -a grep -q "$1" test.txt ]; then
grep "$1" Rail.txt
else
echo not found
fi
Problem:
It says: script.sh: line 3: [: too many arguments every time I run it.
I'm not sure what's wrong with my condition whether I use the wrong operators or parenthesis or square brackets.
At a semi-educated guess, what you want to write is:
if [ "$#" -eq 1 ] && grep -q "$1" test.txt; then
On what ocassions should we use the square brackets?
Historically, the test command, /bin/test was linked to /bin/[, and was an external command, not a shell built-in. These days (and for several decades now), it has been a shell built-in. However, it follows the structure of a command, requiring spaces to separate arguments, and if it is invoked as [, then the last argument must be ].
As to when you use it, you use it when you need to test a condition.
Note that you can write:
if ls; false; true; then echo "the last command counts"; else echo "no it doesn't"; fi
The if command executes a sequence of one or more commands, and tests the exit status of the last command. If the exit status is 0, success, the then clause is executed; if the exit status is not 0, then the else clause is taken.
So, you can use the test when you need to test something. It can be part of an if or elif or while (or until). It can also be used on its own with || or &&:
[ -z "$1" ] && echo "No arguments - or the first argument is an empty string"
[ "$var1" -gt "$var2" ] || echo "Oops!" && exit 1
These could be written as if statements too, of course:
if [ -z "$1" ]
then echo "No arguments - or the first argument is an empty string"
fi
if [ "$var1" -le "$var2" ]
then
echo "Oops!"
exit 1
fi
Note that I needed to invert the test in the second rewrite. Bash has a built-in ! operator which inverts the exit status of the command that follows, so I could also have written:
if ! [ "$var1" -gt "$var2" ]
and test has a ! too, so it could also be written:
if [ ! "$var1" -gt "$var2" ]

bash - Possible to 'override' the test ([[)-builtin?

Is it possible to override Bash's test builtin? So that
[[ $1 = 'a' ]]
not just does the test but also outputs which result was expected when it fails? Something like
echo "Expected $1 to be a.'
EDIT
I know this is bad :-).
The test expression compound command does real short-circuiting that affects all expansions.
$ set -x
$ [[ 0 -gt x=1+1 || ++x -eq $(tee /dev/fd/3 <<<$x) && $(echo 'nope' >&3) ]] 3>&1
+ [[ 0 -gt x=1+1 ]]
++ tee /dev/fd/2
2
+ [[ ++x -eq 2 ]]
So yes you could do anything in a single test expression. In reality it's quite rare to have a test produce a side-effect, and almost never used to produce output.
Also yes, reserved words can be overridden. Bash is more lenient with ksh-style function definitions than POSIX style (which still allows some invalid names).
function [[ { [ "${#:1:${##}-1}" ]; }; \[[ -a -o -a -o -a ]] || echo lulz
Yet another forky bomb.
if function function if function if if \function & then \if & fi && \if & then \function & fi && then \function fi
Something like this?
if [[ $1 == 'a' ]]; then
echo "all right";
else
echo 'Expected $1 to be "a"'
fi
Anyway, what's the point of the test if you only expect one answer? Or do you mean that for debugging purposes?
[[ 'a' = 'a' ]] || echo "failed"
[[ 'b' = 'a' ]] || echo "failed"
failed

Resources