This question already has answers here:
Test if a command outputs an empty string
(13 answers)
Closed 4 years ago.
Some Example:
I've a shellscript where I want to check if a the stdout of a command is empty.
So I can do
if [[ $( whateverbin | wc -c) == 0 ]] ; then
echo no content
fi
But is there no direct command, to check this? Something like :
if whateverbin | checkifstdinisempty ; then
echo no content
fi
You could use the -z conditional expression to test if a string is empty:
if [[ -z $(ls) ]]; then echo "ls returned nothing"; fi
When you run it on an empty result, the branch gets executed:
if [[ -z $(cat non-existing-file) ]]; then echo "there was no result"; fi
Just try to read exactly one character; on no input, read will fail.
if ! whateverbin | IFS= read -n 1; then
echo "No output"
fi
If read fails, the entire pipeline fails, and the ! negates the non-zero exit status so that the entire condition succeeds.
[[ `echo` ]] && echo output found || echo no output
--> no output
[[ `echo something` ]] && echo output found || echo no output
--> output found
With if :
if [ `echo` ] ; then echo ouput found; else echo no output; fi
Related
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
This question already has answers here:
Bash script store command output into variable
(2 answers)
Closed 2 years ago.
I am trying to remove docker container and and store or compare the result but i am unable to do that kindly let me know how to get the return value for further process
delete_the_container() {
res= docker container rm -f $1 && echo "success" || echo "fail"
echo "$?"
}
fielpos="./filename"
input="./filename"
while IFS= read -r line
do
#echo "$line"
res= $(delete_the_container $line)
echo "$res" #not get the res value
done < "$input"
$? returns code value of last command, the code below must help you
docker container rm -f $1
if [[ $? == 0 ]]; then
... code if rm command success ...
else
... code if rm command failed...
fi
Your line (I added $(...) to execute command) :
res= $(docker container rm -f $1 && echo "success" || echo "fail")
res value can be "success" or "fail" but $? are all time 1 because last command are echo.
I am making a script for my school, and I was wondering how I could check a file, and if the string isn't in the file, do a code, but if it is, continue, like this:
while [ -z $(cat File.txt | grep "string" ) ] #Checking if file doesn't contain string
do
echo "No matching string!, trying again" #If it doesn't, run this code
done
echo "String matched!" #If it does, run this code
You can do something like:
$ if grep "string" file;then echo "found";else echo "not found"
To do a loop :
$ while ! grep "no" file;do echo "not found";sleep 2;done
$ echo "found"
But be carefull not to enter an infinite loop. string or file must be changed otherwise the loop has no meaning.
Above if/while works based on the return status of the command and not on the result.
if grep finds string in file will return 0 = success = true
if grep does not find string will return 1 = not success = false
By using ! we revert the "false" to "true" to keep the loop running since while loops on something as soon as it is true.
A more conventional while loop would be similar to your code but without the useless use of cat and the extra pipe:
$ while [ -z $(grep "no" a.txt) ];do echo "not found";sleep 2;done
$ echo "found"
A simple if statement to test if a 'string' is not in file.txt:
#!/bin/bash
if ! grep -q string file.txt; then
echo "string not found in file!"
else
echo "string found in file!"
fi
The -q option (--quiet, --silent) ensures output is not written to standard output.
A simple while loop to test is a 'string' is not in file.txt:
#!/bin/bash
while ! grep -q string file.txt; do
echo "string not found in file!"
done
echo "string found in file!"
Note: be aware of the possibility that the while loop may cause a infinite loop!
Another simple way is to just do the following:
[[ -z $(grep string file.file) ]] && echo "not found" || echo "found"
&& means AND - or execute the following command if the previous is true
|| means OR - or execute if the previous is false
[[ -z $(expansion) ]] means return true if the expansion output is null
This line is much like a double negative, basically:
"return true if the string is not found in file.file; then echo not found if true, or found if false"
Example:
bashPrompt:$ [[ -z $(grep stackOverflow scsi_reservations.sh) ]] && echo "not found" || echo "found"
not found
bashPrompt:$ [[ -z $(grep reservations scsi_reservations.sh) ]] && echo "not found" || echo "found"
found
This question already has answers here:
Why should there be spaces around '[' and ']' in Bash?
(5 answers)
Closed 6 years ago.
I'm having an issue with a script that I am trying to program. Narrowed down and simplified code and it gives an error that command is not found. If i do "test -f file" in command line it returns nothing, not command not found
PATH=$1
#!/bin/bash
DIR=$1
if [[-f $PATH]]; then
echo expression evaluated as true
else
echo expression evaluated as false
fi
exit
Here is the actual more complicated script I'm trying to run
verify()
{
if [[-f $1]]; then
VFY[$2]="f"
echo "$1 is a file"
elif [[-d $1]]
then
VFY[$2]="d"
echo "$1 is a directory"
else
VFY[$2]=0
echo -e "\r"
echo "$1 is neither a file or a directory"
echo -e "\r"
fi
}
Its part of a larger script that can move things around depending on inputs. I've run this in CentOS 6, and FreeBSD, both give the same error "[[-f: Command not found"
Simply add an extra space between [[ and -f, and also before ]].
You will get:
#! /bin/bash
DIR=${1-} # unused in your example
if [[ -f test.sh ]]; then
echo "expression evaluated as true"
else
echo "expression evaluated as false"
fi
exit
and for your function
verify() # file ind
{
local file=$1 ind=$2
if [[ -f "$file" ]]; then
VFY[ind]="f" # no need of $ for ind
echo "$file is a file"
elif [[ -d "$file" ]]; then
VFY[ind]="d"
echo "$file is a directory"
else
VFY[ind]=0
echo -e "\n$file is neither a file or a directory\n"
fi
}
I want to make a build chain script, and I don't want it to perform until the end if there are error during compilation.
It's the first time I write a more "elaborated" script in bash, and it just doesn't work:
it doesn't echo ERROR although I have lines with the word error in it
whatever the value of testError, the script just hangs in the line
this is the code:
testError=false
output=$(scons)
while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then echo 'ERROR' ; $testError = true ; fi #$testError = true fi
done
echo $testError
if $testError ; then exit ; fi;
... other commands
EDIT: Following all posters answers and Bash setting a global variable inside a loop and retaining its value -- Or process substituion for dummies and How do I use regular expressions in bash scripts?,
this is the final version of the code.
It works:
testError=false
shopt -s lastpipe
scons | while read -r line; do
if [[ $line =~ .*[eE]rror.* ]] ; then
echo -e 'ERROR'
testError=true
fi
echo -e '.'
done
if $testError ; then
set -e
fi
You set the value of testError in a subshell induced by your pipeline. When that subshell exits (at the end of the pipeline), any changes you made disappear. Try this:
while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then
echo -e 'ERROR'
testError=true
fi #$testError = true fi
done < <( scons )
or, if you don't want or can't use process substitution, use a temporary file
scons > tmp
while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then
echo -e 'ERROR'
testError=true
fi #$testError = true fi
done < tmp
This eliminates the pipeline, so the changes to testError persist after the while loop.
And, if your version of bash is new enough (4.2 or later), there is an option that allows the while loop at the end of a pipeline to execute in the current shell, not a subshell.
shopt -s lastpipe
scons | while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then
echo -e 'ERROR'
testError=true
fi #$testError = true fi
done
You should try
set -e
this stops the script to continue if a command exit with a non zero status
or better
error_case() { # do something special; }
trap 'echo >&2 "an error occurs"; error_case' ERR
this run error_case function each time a command exit with a non zero status
See http://mywiki.wooledge.org/BashFAQ/105
Another bug is that you have spaces in the assignment. And skip the $
$testError = true
should be
testError=true
EDIT
testerror is changed in the subshell. Try
testerror=$(
scons | while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then
echo true
fi #$testError = true fi
done
)
Are you trying to parse the output of scons?
This:
output=$(scons)
while read -r line; do
if [[ $line == .*[eE]rror.* ]] ; then
echo 'ERROR'
testError=true
fi
done
does not do that. Perhaps you want:
scons | while read -r line; do ... ; done
I answer also because other answers didn't notice: the use of regular expression should be done this way, using =~and not ==:
if [[ $line =~ .*[eE]rror.* ]] ; then
...
cf How do I use regular expressions in bash scripts?