Is it possible to have two expressions in a bash if statement, using an 'or' operator, in which one of the expressions tests the exit status of grep?
For example, I want a script to process all directories in my PATH except for '.' and /home/$LOGNAME/bin. I can do it fine with two if statements, but I'd like to combine the two tests into a single statement joined by 'or'.
This is the working version (two separate if statements):
IFS=:
for VAR in $PATH ; do
if echo $VAR | grep /home/$LOGNAME/bin > /dev/null
then
echo SKIPPING YOUR OWN bin DIRECTORY \($VAR\)
elif [ "$VAR" = "." ]
then
echo SKIPPING CURRENT WORKING DIRECTORY \($VAR\)
else
echo processing $VAR
fi
done
.. which produces the following output:
SKIPPING CURRENT WORKING DIRECTORY (.)
SKIPPING YOUR OWN bin DIRECTORY (/home/bobo/bin)
processing /home/qa/utils
processing /usr/lib64/qt-3.3/bin
processing /usr/local/bin
processing /bin
processing /usr/bin
processing /usr/local/sbin
processing /usr/sbin
processing /sbin
Just a few of many failed attempts:
if [ ( echo $VAR | grep /home/$LOGNAME > /dev/null )] -o [ "$VAR" = "." ]
if [[ ( echo $VAR | grep /home/$LOGNAME > /dev/null ) ]] -o [[ "$VAR" = "." ]]
if ( echo $VAR | grep /home/$LOGNAME > /dev/null ) -o "$VAR" = "."
if ( echo $VAR | grep /home/$LOGNAME > /dev/null ) || "$VAR" = "."
The proper form for your if elif fi block can be like this. You also don't need to use an external binary command like grep.
IFS=:
for VAR in $PATH; do
if [[ $VAR == . ]]; then
echo "SKIPPING CURRENT WORKING DIRECTORY \($VAR\)"
elif [[ "$VAR" == "/home/$LOGNAME/bin"* ]]; then
echo "SKIPPING YOUR OWN bin DIRECTORY \($VAR\)"
else
echo "processing $VAR"
fi
done
If you want to do it with one line you could have:
IFS=:
for VAR in $PATH; do
if [[ $VAR != . && "$VAR" != "/home/$LOGNAME/bin"* ]]; then
echo "processing $VAR"
else
echo "SKIPPING CURRENT WORKING DIRECTORY OR YOUR OWN bin DIRECTORY \($VAR\)"
fi
done
Or
IFS=:
for VAR in $PATH; do
if [[ $VAR == . || "$VAR" == "/home/$LOGNAME/bin"* ]]; then
echo "SKIPPING CURRENT WORKING DIRECTORY OR YOUR OWN bin DIRECTORY \($VAR\)"
else
echo "processing $VAR"
fi
done
The else block is optional.
It is possible, but totally unnecessary. Just let if evaluate the exit status of grep directly:
if echo "$var" | grep -q "$HOME/bin" || test "$var" = . ; then
...
fi
or
if echo "$var" | grep -q "^$HOME"'/bin$\|^\.$'; then ...
Note that the semantics of these are slightly different, since the second requires that $HOME/bin be the entire line, but that is probably what you want. (The anchors should be in the first example, if that is the case.)
There are several ways to do this. Using your if construct, it could look something like this (although there are a couple other equally valid ways to do it):
if [[ "${VAR}" == "." ]] || grep -q /home/${LOGNAME}/bin <<< "${VAR}"
You might also consider something like this, which I think makes it a bit clearer what you're doing, and might even be less typing (it also dispenses with calling an external program, so will be slightly more efficient):
case "${VAR}" in
.|*/home/${LOGNAME}/bin*) .... ;;
*) .... ;;
esac
Related
I am taking baby steps at learning bash and I am developing a piece of code which takes an input and checks if it contains any spaces. The idea is that the variable should NOT contain any spaces and the code should consequently echo a suitable message.
Try this:
#!/bin/bash
if [[ $1 = *[[:space:]]* ]]
then
echo "space exist"
fi
You can use grep, like this:
echo " foo" | grep '\s' -c
# 1
echo "foo" | grep '\s' -c
# 0
Or you may use something like this:
s=' foo'
if [[ $s =~ " " ]]; then
echo 'contains space'
else
echo 'ok'
fi
You can test simple glob patterns in portable shell by using case, without needing any external programs or Bash extensions (that's a good thing, because then your scripts are useful to more people).
#!/bin/sh
case "$1" in
*' '*)
printf 'Invalid argument %s (contains space)\n' "$1" >&2
exit 1
;;
esac
You might want to include other whitespace characters in your check - in which case, use *[[:space:]]* as the pattern instead of *' '*.
You can use wc -w command to check if there are any words. If the result of this output is a number greater than 1, then it means that there are more than 1 words in the input. Here's an example:
#!/bin/bash
read var1
var2=`echo $var1 | wc -w`
if [ $var2 -gt 1 ]
then
echo "Spaces"
else
echo "No spaces"
fi
Note: there is a | (pipe symbol) which means that the result of echo $var1 will be given as input to wc -w via the pipe.
Here is the link where I tested the above code: https://ideone.com/aKJdyN
You could use parameter expansion to remove everything that isn't a space and see if what's left is the empty string or not:
var1='has space'
var2='nospace'
for var in "$var1" "$var2"; do
if [[ ${var//[^[:space:]]} ]]; then
echo "'$var' contains a space"
fi
done
The key is [[ ${var//[^[:space:]]} ]]:
With ${var//[^[:space:]]}, everything that isn't a space is removed from the expansion of $var.
[[ string ]] has a non-zero exit status if string is empty. It's a shorthand for the equivalent [[ -n string ]].
We could also quote the expansion of ${var//[^[:space:]]}, but [[ ... ]] takes care of the quoting for us.
I have a piece of code to find the first file in a directory:
bash~> $( echo eval "ls | head -1" )
arguprog.sh
This snippet was then added to an if statement, to run a different set of commands if that file was arguprog.sh:
bash~> if [[ $( echo eval "ls | head -1" ) == "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi;
FALSE
However this is not doing what I want. It returns FALSE even though the first file is arguprog.sh!
Is there a way to resolve this while still doing the string comparison entirely within the test block?
First, eval is evil, especially when it's not needed. In your case, eval is not needed!
Replace the coding horror you showed with just:
ls | head -1
and to include it in your test statement:
if [[ $(ls | head -1) = "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi
But this is wrong and broken (see below).
Now something more general: do not parse the output of ls. If you want to find the first file (or directory or...) in your current dir, use globs and this method:
shopt -s nullglob
files=( * )
# The array files contains the names of all the files (and directories...)
# in the current directory, sorted by name.
# The first one is given by the expansion of "${files[0]}". So:
if [[ "${files[0]}" = "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi
Notice that your method, parsing ls is wrong. Look:
$ # Create a new scratch dir
$ mkdir myscratchdir
$ # Go in there
$ cd myscratchdir
$ # touch a few files:
$ touch $'arguprog.sh\nwith a newline' "some other file"
$ # I created 2 files, none of them is exactly arguprog.sh. Now look:
$ if [[ $(ls | head -1) = "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi
TRUE
$ # HORROR!
There are twisted work-arounds for this, but really, the best method is the one I just gave you.
Done!
The value of $( echo eval "ls | head -1" ) is "eval ls | head -1" not "arguprog.sh", hence why you get FALSE.
Take a look at this:
$ a=$( echo eval "ls | head -1" )
$ echo $a
eval ls | head -1
The reason you see arguprog.sh when you run bash~> $( echo eval "ls | head -1" ) is because bash executes the command which is eval ls | head -1 and returns the result which is arguprog.sh.
In order to do the same in your if-statement, you need to execute it as well by enclosing it in another set of $(...), like this:
$ if [[ $($( echo eval "ls | head -1" )) == "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi;
TRUE
But don't do that! It is a lot easier to simply use:
$ if [[ $(ls | head -1) == "arguprog.sh" ]]; then echo "TRUE"; else echo "FALSE"; fi;
TRUE
Also note that parsing the output of ls is not good practice. See ParsingLs for details.
Try this way:
if [[ `ls | head -1` = "04 dynamic-programming.pdf" ]]; then
echo "TRUE"
else
echo "FALSE"
fi
You should merely use = instead of == and skip the eval part.
I want to check if a file contains a specific string or not in bash. I used this script, but it doesn't work:
if [[ 'grep 'SomeString' $File' ]];then
# Some Actions
fi
What's wrong in my code?
if grep -q SomeString "$File"; then
Some Actions # SomeString was found
fi
You don't need [[ ]] here. Just run the command directly. Add -q option when you don't need the string displayed when it was found.
The grep command returns 0 or 1 in the exit code depending on
the result of search. 0 if something was found; 1 otherwise.
$ echo hello | grep hi ; echo $?
1
$ echo hello | grep he ; echo $?
hello
0
$ echo hello | grep -q he ; echo $?
0
You can specify commands as an condition of if. If the command returns 0 in its exitcode that means that the condition is true; otherwise false.
$ if /bin/true; then echo that is true; fi
that is true
$ if /bin/false; then echo that is true; fi
$
As you can see you run here the programs directly. No additional [] or [[]].
In case if you want to check whether file does not contain a specific string, you can do it as follows.
if ! grep -q SomeString "$File"; then
Some Actions # SomeString was not found
fi
In addition to other answers, which told you how to do what you wanted, I try to explain what was wrong (which is what you wanted.
In Bash, if is to be followed with a command. If the exit code of this command is equal to 0, then the then part is executed, else the else part if any is executed.
You can do that with any command as explained in other answers: if /bin/true; then ...; fi
[[ is an internal bash command dedicated to some tests, like file existence, variable comparisons. Similarly [ is an external command (it is located typically in /usr/bin/[) that performs roughly the same tests but needs ] as a final argument, which is why ] must be padded with a space on the left, which is not the case with ]].
Here you needn't [[ nor [.
Another thing is the way you quote things. In bash, there is only one case where pairs of quotes do nest, it is "$(command "argument")". But in 'grep 'SomeString' $File' you have only one word, because 'grep ' is a quoted unit, which is concatenated with SomeString and then again concatenated with ' $File'. The variable $File is not even replaced with its value because of the use of single quotes. The proper way to do that is grep 'SomeString' "$File".
Shortest (correct) version:
grep -q "something" file; [ $? -eq 0 ] && echo "yes" || echo "no"
can be also written as
grep -q "something" file; test $? -eq 0 && echo "yes" || echo "no"
but you dont need to explicitly test it in this case, so the same with:
grep -q "something" file && echo "yes" || echo "no"
##To check for a particular string in a file
cd PATH_TO_YOUR_DIRECTORY #Changing directory to your working directory
File=YOUR_FILENAME
if grep -q STRING_YOU_ARE_CHECKING_FOR "$File"; ##note the space after the string you are searching for
then
echo "Hooray!!It's available"
else
echo "Oops!!Not available"
fi
grep -q [PATTERN] [FILE] && echo $?
The exit status is 0 (true) if the pattern was found; otherwise blankstring.
if grep -q [string] [filename]
then
[whatever action]
fi
Example
if grep -q 'my cat is in a tree' /tmp/cat.txt
then
mkdir cat
fi
In case you want to checkif the string matches the whole line and if it is a fixed string, You can do it this way
grep -Fxq [String] [filePath]
example
searchString="Hello World"
file="./test.log"
if grep -Fxq "$searchString" $file
then
echo "String found in $file"
else
echo "String not found in $file"
fi
From the man file:
-F, --fixed-strings
Interpret PATTERN as a list of fixed strings, separated by newlines, any of
which is to be matched.
(-F is specified by POSIX.)
-x, --line-regexp
Select only those matches that exactly match the whole line. (-x is specified by
POSIX.)
-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero
status if any match is
found, even if an error was detected. Also see the -s or --no-messages
option. (-q is specified by
POSIX.)
Try this:
if [[ $(grep "SomeString" $File) ]] ; then
echo "Found"
else
echo "Not Found"
fi
I done this, seems to work fine
if grep $SearchTerm $FileToSearch; then
echo "$SearchTerm found OK"
else
echo "$SearchTerm not found"
fi
grep -q "something" file
[[ !? -eq 0 ]] && echo "yes" || echo "no"
How does one test for the existence of files in a directory using bash?
if ... ; then
echo 'Found some!'
fi
To be clear, I don't want to test for the existence of a specific file. I would like to test if a specific directory contains any files.
I went with:
(
shopt -s dotglob nullglob
existing_files=( ./* )
if [[ ${#existing_files[#]} -gt 0 ]] ; then
some_command "${existing_files[#]}"
fi
)
Using the array avoids race conditions from reading the file list twice.
From the man page:
-f file
True if file exists and is a regular file.
So:
if [ -f someFileName ]; then echo 'Found some!'; fi
Edit: I see you already got the answer, but for completeness, you can use the info in Checking from shell script if a directory contains files - and lose the dotglob option if you want hidden files ignored.
I typically just use a cheap ls -A to see if there's a response.
Pseudo-maybe-correct-syntax-example-ahoy:
if [[ $(ls -A my_directory_path_variable ) ]] then....
edit, this will work:
myDir=(./*) if [ ${#myDir[#]} -gt 1 ]; then echo "there's something down here"; fi
You can use ls in an if statement thus:
if [[ "$(ls -a1 | egrep -v '^\.$|^\.\.$')" = "" ]] ; then echo empty ; fi
or, thanks to ikegami,
if [[ "$(ls -A)" = "" ]] ; then echo empty ; fi
or, even shorter:
if [[ -z "$(ls -A)" ]] ; then echo empty ; fi
These basically list all files in the current directory (including hidden ones) that are neither . nor ...
If that list is empty, then the directory is empty.
If you want to discount hidden files, you can simplify it to:
if [[ "$(ls)" = "" ]] ; then echo empty ; fi
A bash-only solution (no invoking external programs like ls or egrep) can be done as follows:
emp=Y; for i in *; do if [[ $i != "*" ]]; then emp=N; break; fi; done; echo $emp
It's not the prettiest code in the world, it simply sets emp to Y and then, for every real file, sets it to N and breaks from the for loop for efficiency. If there were zero files, it stays as Y.
Try this
if [ -f /tmp/foo.txt ]
then
echo the file exists
fi
ref: http://tldp.org/LDP/abs/html/fto.html
you may also want to check this out: http://tldp.org/LDP/abs/html/fto.html
How about this for whether directory is empty or not
$ find "/tmp" -type f -exec echo Found file {} \;
#!/bin/bash
if [ -e $1 ]; then
echo "File exists"
else
echo "Files does not exist"
fi
I don't have a good pure sh/bash solution, but it's easy to do in Perl:
#!/usr/bin/perl
use strict;
use warnings;
die "Usage: $0 dir\n" if scalar #ARGV != 1 or not -d $ARGV[0];
opendir my $DIR, $ARGV[0] or die "$ARGV[0]: $!\n";
my #files = readdir $DIR;
closedir $DIR;
if (scalar #files == 2) { # . and ..
exit 0;
}
else {
exit 1;
}
Call it something like emptydir and put it somewhere in your $PATH, then:
if emptydir dir ; then
echo "dir is empty"
else
echo "dir is not empty"
fi
It dies with an error message if you give it no arguments, two or more arguments, or an argument that isn't a directory; it's easy enough to change if you prefer different behavior.
# tested on Linux BASH
directory=$1
if test $(stat -c %h $directory) -gt 2;
then
echo "not empty"
else
echo "empty"
fi
For fun:
if ( shopt -s nullglob ; perl -e'exit !#ARGV' ./* ) ; then
echo 'Found some!'
fi
(Doesn't check for hidden files)
(This is debian squeeze amd64)
I need to test if a file is a member of a list of files.
So long my (test) script is:
set -x
array=$( ls )
echo $array
FILE=log.out
# This line gives error!
if $FILE in $array
then echo "success!"
else echo "bad!"
fi
exit 0
¿Any ideas?
Thanks for all the responses. To clarify: The script given is only an example, the actual problem is more complex. In the final solution, it will be done within a loop, so I need the file(name) to be tested for to be in a variable.
Thanks again. No my test-script works, and reads:
in_list() {
local search="$1"
shift
local list=("$#")
for file in "${list[#]}" ; do
[[ "$file" == "$search" ]] && return 0
done
return 1
}
#
# set -x
array=( * ) # Array of files in current dir
# echo $array
FILE="log.out"
if in_list "$FILE" "${array[#]}"
then echo "success!"
else echo "bad!"
fi
exit 0
if ls | grep -q -x t1 ; then
echo Success
else
echo Failure
fi
grep -x matches full lines only, so ls | grep -x only returns something if the file exists.
If you just want to check if a file exists, then
[[ -f "$file" ]] && echo yes || echo no
If your array contains a list of files generated by some means other than ls, then you have to iterate over it as demonstrated by Sorpigal.
How about
in_list() {
local search="$1"
shift
local list=("$#")
for file in "${list[#]}" ; do
[[ $file == $search ]] && return 0
done
return 1
}
if in_list log.out * ; then
echo 'success!'
else
echo 'bad!'
fi
EDIT: made it a bit less idiotic.
EDIT #2:
Of course if all you're doing is looking in the current directory to see if a particular file is there, which is effectively what the above is doing, then you can just say
[ -e log.out ] && echo 'success!' || echo 'bad!'
If you're actually doing something more complicated involving lists of files then this might not be sufficient.