Need some help writing an if statement in UNIX bash scripting - bash

I'm writing a reasonably lengthy script (or what I would consider lengthy - you could probably do it in a few hours). I basically have a file (named .restore.info) which contains files of names. In part of the script, I want to test "If cannot find filename in .restore.info, then says “Error: restored file does not exist”. Apologies if this doesn't make sense for you guys (for me, it does in the grand scheme of things). So if type this in the command line:
sh MYSCRIPT filename
It will search for the string filename in the .restore.info file, and if it cant find anything, it should produce an error message.
Basically, I need the top line of this coded translated into a UNIX bash statement and something that actually makes sense!:
If grep $1 .restore.info returns an exist status of 1; then
echo “Filename does not exist!”
fi
Thanks in advance! Please ask me if you need me to clarify anything more clearly as I know I'm not the best explainer, and I'll get back to you in less than a minute! (+rep and best answer of course will be given!)

You probably only care if grep exits with a non-zero exit status:
if ! grep -q "$1" .restore.info; then
echo "Filename does not exist!"
fi
but if you really do care about a specific exit status (1, in this case):
if ! grep -q "$1" .restore.info && [[ $? -eq 1 ]]; then
echo "Filename does not exist!"
fi

Use grep -q
grep -q "filename" .restore.info && echo "found match"
or
! grep -q "filename" .restore.info && echo "not found"

grep -l 'filename' .restore.info
if [ $? = 0 ];then
echo "found it"
else
echo "not found"
fi

Related

Why does $# always return 0?

I'm trying to write a script that will only accept exactly one argument. I'm still learning so I don't understand what's wrong with my code. I don't understand why, even though I change the number of inputs the code just exits. (Note: I'm going to use $dir for later if then statements but I haven't included it.)
#!/bin/bash
echo -n "Specify the name of the directory"
read dir
if [ $# -ne 1 ]; then
echo "Script requires one and only one argument"
exit
fi
You can use https://www.shellcheck.net/ to double check your syntax.
$# tells you how many arguments the script was called with.
Here you have two options.
Option 1: Use arguments
#!/bin/bash
if [[ $# -ne 1 ]]
then
echo "Script requires one and only one argument"
exit 1
else
echo "ok, arg1 is $1"
fi
To call the script do: ./script.bash argument
Use [[ ]] for testing conditions (http://mywiki.wooledge.org/BashFAQ/031)
exit 1: by default when a script exists with a 0 status code, it means it worked ok. Here since it is an error, specify a non-zero value.
Option 2: Do not use arguments, ask the user for a value.
Note: this version does not use arguments at all.
#!/bin/bash
read -r -p "Specify the name of the directory: " dir
if [[ ! -d "$dir" ]]
then
echo "Error, directory $dir does not exist."
exit 1
else
echo "ok, directory $dir exists."
fi
To call the script do: ./script.bash without any arguments.
You should research bash tutorials to learn how to use arguments.

bash script with if [[ ! -f path_to_files ]]

On MacOS Catalina, I have a bash script with
if [[ ! -f $CR/home/files/Recovery_*.txt ]]
then
echo "File does not exists in /home/files directory. Exiting" >> $log
echo "Aborted - $CR/home/files/Recovery_*txt not exist"
exit
fi
Even though there are 2 files in the directory the script exits. If I list directory contents beforehand there are 2 files. If I change it as follows the if is skipped:
if [[ `ls -la $CR/home/files/Recovery_*.txt | wc -l` -eq 0 ]]
then
echo "No files are :"
exit
fi
I am wanting to use the if -f conditional.
Any suggestions please?
Cheers, C
If you use nullglob, a glob expression that doesn't match returns an empty string. This lets us count files in bash without spawning other processes. Create an array with the expression, then check its length.
shopt -s nullglob
files=($CR/home/files/Recovery_*.txt)
if [[ ${#files[#]} -eq 0 ]]
then
echo "No files"
exit
fi
[Edited]
The error was not the variables, but the missing shebang as the script has come across from W2K3 SFU.
The tip about shellchecker.net was awesome and I will use that from now.
Thanks.

Bash script for checking a value against line of values in a file

I have a script in Linux that will check a number inputted from the parameter. The number in this $status may vary. The script code after the is checking whether the code in $status is within a group of code or not. If yes, then echo "It exist". If not, then echo "Does not exist."
The problem is the group of code can change, and it's a pain to change the code conditional to reflect those changes. So I want to put all that group of numbers in a file.
For example:
The script check.sh:
$filecheck = "check.txt"
$status = $1
if [ $status EXISTS WITHIN $filecheck ]; then
echo "It exists."
else
echo "Does not exist."
fi
The check.txt contents:
201
5099
305
8670
2
582
9582
Run: # ./check.sh 305
Result: It exists.
Run: # ./check.sh 689
Result: Does not exist.
The question is, how should I formulate the conditional? I felt this should be not hard and involve grep, but I don't know how to formulate question about this in google to get the result I need, and I'm very beginner at bash script and Linux too. Actually the script is more than just this, but I only need the conditional part, so I simplified this down. Thanks.
Use grep:
if grep -q "$1" "$filecheck" >/dev/null 2>&1; then
echo "it exists"
else
echo "it does not exist"
fi
It doesn't look like a bash script but I think I got your question. You want to find if a given number exists in the list. To do that 'grep -w ...' should work for you, which looks for exact match. Note that it assumes that each number is on different line.
So your shell script would look like following:
filecheck="check.txt"
status=$1
grep -w $status $filecheck >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo "It exists."
else
echo "Does not exist."
fi

Bash + check for file exist with a path home ~

I haven't found anything to deal with this particular situation. Maybe there is a easy way that I'm overlooking instead of checking for a string to catch this scenario. When I check an input for existence of a file, if the input is ~/filecheck , this won't work. I get negative results while the file is in my home folder. Any suggestions for improvement to any part of the script I will definitely appreciate. I also have to use an input instead of a argument. Thanks for any help.
my test script
read -p "Enter: " input
echo $input
if [ -f $input ]; then
read -p "Do you REALLY want to delete this file?:" input2
if [[ $input2='y' || $input2 = 'Y' ]]
then
rm -f $input
elif [[ $input2='n' || $input2='N' ]]
then
exit
else
echo "Invaild Option"
exit
fi
else
echo Invaild Option!
exit
fi
Since you are entering input string as ~/filecheck shell doesn't expand tilde while using condition with -f in [ -f $input ]
You can use it this way but it is not recommended and potentially dangerous as arbitrary commands can be run by user:
if [[ -f $(bash -c "echo $input") ]]; then
echo "file exists"
fi
EDIT: As per the comments below to avoid risky bash -c you can use:
if [[ -f "${input/\~/$HOME}" ]]; then
echo "file exists"
fi
You can't have tilde expansion in this part of the program without using something based on eval—and you don't want to do that with user input. So, your poor-man solution will be to substitute any potential leading ~/ with the expansion of $HOME/. Here's the adaptation of your script in an arguably better style:
#!/bin/bash
read -e -p "Enter: " input
input=${input/#~\//$HOME/} # <--- this is the main idea of this answer (and it's rather poor)
echo "$input"
if [[ -f $input ]]; then
read -e -p "Do you REALLY want to delete this file? " input2
if [[ ${input2,,} = y ]]; then
rm -f -- "$input"
elif [[ ${input2,,} = n ]]; then
exit
else
echo "Invalid Option"
exit
fi
else
echo "Invalid Option!"
fi
exit
Now, out of curiosity, why are you spending time to make a wrapper around rm? you're making a clunky interface to an already existing program, without adding anything to it, only rendering it less powerful and less easy to use.
If all what you want it's to ask the user before deleting, you can use:
rm -i
This will give you appropriate error in the case file does not exist.

Test if a command outputs an empty string

How can I test if a command outputs an empty string?
Previously, the question asked how to check whether there are files in a directory. The following code achieves that, but see rsp's answer for a better solution.
Empty output
Commands don’t return values – they output them. You can capture this output by using command substitution; e.g. $(ls -A). You can test for a non-empty string in Bash like this:
if [[ $(ls -A) ]]; then
echo "there are files"
else
echo "no files found"
fi
Note that I've used -A rather than -a, since it omits the symbolic current (.) and parent (..) directory entries.
Note: As pointed out in the comments, command substitution doesn't capture trailing newlines. Therefore, if the command outputs only newlines, the substitution will capture nothing and the test will return false. While very unlikely, this is possible in the above example, since a single newline is a valid filename! More information in this answer.
Exit code
If you want to check that the command completed successfully, you can inspect $?, which contains the exit code of the last command (zero for success, non-zero for failure). For example:
files=$(ls -A)
if [[ $? != 0 ]]; then
echo "Command failed."
elif [[ $files ]]; then
echo "Files found."
else
echo "No files found."
fi
More info here.
TL;DR
if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then ...; fi
Thanks to netj
for a suggestion to improve my original:if [[ $(ls -A | wc -c) -ne 0 ]]; then ...; fi
This is an old question but I see at least two things that need some improvement or at least some clarification.
First problem
First problem I see is that most of the examples provided here simply don't work. They use the ls -al and ls -Al commands - both of which output non-empty strings in empty directories. Those examples always report that there are files even when there are none.
For that reason you should use just ls -A - Why would anyone want to use the -l switch which means "use a long listing format" when all you want is test if there is any output or not, anyway?
So most of the answers here are simply incorrect.
Second problem
The second problem is that while some answers work fine (those that don't use ls -al or ls -Al but ls -A instead) they all do something like this:
run a command
buffer its entire output in RAM
convert the output into a huge single-line string
compare that string to an empty string
What I would suggest doing instead would be:
run a command
count the characters in its output without storing them
or even better - count the number of maximally 1 character using head -c1(thanks to netj for posting this idea in the comments below)
compare that number with zero
So for example, instead of:
if [[ $(ls -A) ]]
I would use:
if [[ $(ls -A | wc -c) -ne 0 ]]
# or:
if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]
Instead of:
if [ -z "$(ls -lA)" ]
I would use:
if [ $(ls -lA | wc -c) -eq 0 ]
# or:
if [ $(ls -lA | head -c1 | wc -c) -eq 0 ]
and so on.
For small outputs it may not be a problem but for larger outputs the difference may be significant:
$ time [ -z "$(seq 1 10000000)" ]
real 0m2.703s
user 0m2.485s
sys 0m0.347s
Compare it with:
$ time [ $(seq 1 10000000 | wc -c) -eq 0 ]
real 0m0.128s
user 0m0.081s
sys 0m0.105s
And even better:
$ time [ $(seq 1 10000000 | head -c1 | wc -c) -eq 0 ]
real 0m0.004s
user 0m0.000s
sys 0m0.007s
Full example
Updated example from the answer by Will Vousden:
if [[ $(ls -A | wc -c) -ne 0 ]]; then
echo "there are files"
else
echo "no files found"
fi
Updated again after suggestions by netj:
if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then
echo "there are files"
else
echo "no files found"
fi
Additional update by jakeonfire:
grep will exit with a failure if there is no match. We can take advantage of this to simplify the syntax slightly:
if ls -A | head -c1 | grep -E '.'; then
echo "there are files"
fi
if ! ls -A | head -c1 | grep -E '.'; then
echo "no files found"
fi
Discarding whitespace
If the command that you're testing could output some whitespace that you want to treat as an empty string, then instead of:
| wc -c
you could use:
| tr -d ' \n\r\t ' | wc -c
or with head -c1:
| tr -d ' \n\r\t ' | head -c1 | wc -c
or something like that.
Summary
First, use a command that works.
Second, avoid unnecessary storing in RAM and processing of potentially huge data.
The answer didn't specify that the output is always small so a possibility of large output needs to be considered as well.
if [ -z "$(ls -lA)" ]; then
echo "no files found"
else
echo "There are files"
fi
This will run the command and check whether the returned output (string) has a zero length.
You might want to check the 'test' manual pages for other flags.
Use the "" around the argument that is being checked, otherwise empty results will result in a syntax error as there is no second argument (to check) given!
Note: that ls -la always returns . and .. so using that will not work, see ls manual pages. Furthermore, while this might seem convenient and easy, I suppose it will break easily. Writing a small script/application that returns 0 or 1 depending on the result is much more reliable!
For those who want an elegant, bash version-independent solution (in fact should work in other modern shells) and those who love to use one-liners for quick tasks. Here we go!
ls | grep . && echo 'files found' || echo 'files not found'
(note as one of the comments mentioned, ls -al and in fact, just -l and -a will all return something, so in my answer I use simple ls
Bash Reference Manual
6.4 Bash Conditional Expressions
-z string
True if the length of string is zero.
-n string
string
True if the length of string is non-zero.
You can use shorthand version:
if [[ $(ls -A) ]]; then
echo "there are files"
else
echo "no files found"
fi
As Jon Lin commented, ls -al will always output (for . and ..). You want ls -Al to avoid these two directories.
You could for example put the output of the command into a shell variable:
v=$(ls -Al)
An older, non-nestable, notation is
v=`ls -Al`
but I prefer the nestable notation $( ... )
The you can test if that variable is non empty
if [ -n "$v" ]; then
echo there are files
else
echo no files
fi
And you could combine both as if [ -n "$(ls -Al)" ]; then
Sometimes, ls may be some shell alias. You might prefer to use $(/bin/ls -Al). See ls(1) and hier(7) and environ(7) and your ~/.bashrc (if your shell is GNU bash; my interactive shell is zsh, defined in /etc/passwd - see passwd(5) and chsh(1)).
I'm guessing you want the output of the ls -al command, so in bash, you'd have something like:
LS=`ls -la`
if [ -n "$LS" ]; then
echo "there are files"
else
echo "no files found"
fi
sometimes "something" may come not to stdout but to the stderr of the testing application, so here is the fix working more universal way:
if [[ $(partprobe ${1} 2>&1 | wc -c) -ne 0 ]]; then
echo "require fixing GPT parititioning"
else
echo "no GPT fix necessary"
fi
Here's a solution for more extreme cases:
if [ `command | head -c1 | wc -c` -gt 0 ]; then ...; fi
This will work
for all Bourne shells;
if the command output is all zeroes;
efficiently regardless of output size;
however,
the command or its subprocesses will be killed once anything is output.
All the answers given so far deal with commands that terminate and output a non-empty string.
Most are broken in the following senses:
They don't deal properly with commands outputting only newlines;
starting from Bash≥4.4 most will spam standard error if the command output null bytes (as they use command substitution);
most will slurp the full output stream, so will wait until the command terminates before answering. Some commands never terminate (try, e.g., yes).
So to fix all these issues, and to answer the following question efficiently,
How can I test if a command outputs an empty string?
you can use:
if read -n1 -d '' < <(command_here); then
echo "Command outputs something"
else
echo "Command doesn't output anything"
fi
You may also add some timeout so as to test whether a command outputs a non-empty string within a given time, using read's -t option. E.g., for a 2.5 seconds timeout:
if read -t2.5 -n1 -d '' < <(command_here); then
echo "Command outputs something"
else
echo "Command doesn't output anything"
fi
Remark. If you think you need to determine whether a command outputs a non-empty string, you very likely have an XY problem.
Here's an alternative approach that writes the std-out and std-err of some command a temporary file, and then checks to see if that file is empty. A benefit of this approach is that it captures both outputs, and does not use sub-shells or pipes. These latter aspects are important because they can interfere with trapping bash exit handling (e.g. here)
tmpfile=$(mktemp)
some-command &> "$tmpfile"
if [[ $? != 0 ]]; then
echo "Command failed"
elif [[ -s "$tmpfile" ]]; then
echo "Command generated output"
else
echo "Command has no output"
fi
rm -f "$tmpfile"
Sometimes you want to save the output, if it's non-empty, to pass it to another command. If so, you could use something like
list=`grep -l "MY_DESIRED_STRING" *.log `
if [ $? -eq 0 ]
then
/bin/rm $list
fi
This way, the rm command won't hang if the list is empty.
As mentioned by tripleee in the question comments , use moreutils ifne (if input not empty).
In this case we want ifne -n which negates the test:
ls -A /tmp/empty | ifne -n command-to-run-if-empty-input
The advantage of this over many of the another answers when the output of the initial command is non-empty. ifne will start writing it to STDOUT straight away, rather than buffering the entire output then writing it later, which is important if the initial output is slowly generated or extremely long and would overflow the maximum length of a shell variable.
There are a few utils in moreutils that arguably should be in coreutils -- they're worth checking out if you spend a lot of time living in a shell.
In particular interest to the OP may be dirempty/exists tool which at the time of writing is still under consideration, and has been for some time (it could probably use a bump).

Resources