BASH: determine which of three strings is in a file - bash

I need to determine which of three strings is found in a file. It is guaranteed that only one of the three is found in the file. Then I want to do a different thing based on which of the three is in the file.
I am currently trying to do:
myfile="/home/directory/file.xml"
case "stringOne" in
*$myfile*)
#Do thing A
;;
esac
case "stringTwo" in
*$myfile*)
#Do thing B
;;
esac
case "stringThree" in
*$myfile*)
#Do thing C
;;
esac
however, this is not working, and my program gets stuck. Is there a better way, or a quick way to fix this way?

Since the file is guaranteed to contain one of them, you can find which one it is and the use case statements:
myfile="/home/directory/file.xml"
str=$(grep -o -m 1 -E 'stringOne|stringTwo|stringThree' $myfile)
[[ -z ${str} ]] && { echo "No match found!"; exit 1; }
case "${str}" in
stringOne)
#Do thing A
;;
stringTwo)
#Do thing B
;;
stringThree)
#Do thing C
;;
esac
grep options:
-o will print only the matching word. So you know which word is there in the input file.
-m 1 will ensure it stops at first match. So it doesn't need to scan the rest of the file.
-E is for regex match that match which is to match either one of the three strings.

You can read the file data once and then check each string's presence:
data=$(</home/directory/file.xml)
if [[ $data == *stringOne* ]]; then
echo "process stringOne"
elif [[ $data == *stringTwo* ]]; then
echo "process stringTwo"
elif [[ $data == *stringThree* ]]; then
echo "process stringThree"
fi

Related

Validate bash script arguments

I am trying to do something like this to make a script to perform backups if they have failed. I am taking in the environment as argument to the script.
The one thing i am unsure on how to do is that i want to verify $1 to only include some predefined values. The predefined values should be something like tst, prd, qa, rpt. Anyone?
#!/bin/bash
ENVIRONMENT=$1
BACKUPDATE=$(date +"%d_%m_%Y")
BACKUPFILE="$ENVIRONMENT".backup."$BACKUPDATE".tar.gz
if [ $1 == "" ]
then
echo "No environment specified"
exit
elif [ -f "$BACKUPFILE" ]; then
echo "The file '$BACKUPFILE' exists."
else
echo "The file '$BACKUPFILE' in not found."
exec touch "$BACKUPFILE"
fi
You can use case:
case "$1" in
tst) echo "Backing up Test style" ;;
prd)
echo "Production backup"
/etc/init.d/myservice stop
tar czf ...
/etc/init.d/myservice start
;;
qa) echo "Quality skipped" ;;
rpt)
echo "Different type of backup"
echo "This could be another processing"
...
;;
*)
echo "Unknown backup type"
exit 2
;;
esac
Note the double ;; to end each case, and the convenient use of pattern matching.
Edit: following your comment and #CharlesDuffy suggestion, if you want to have all valid options in an array and test your value against any of them (hence having the same piece of code for all valid values), you can use an associative array:
declare -A valids=(["tst"]=1 ["prd"]=1 ["qa"]=1 ["rpt"]=1)
if [[ -z ${valids[$1]} ]] ; then
echo "Invalid parameter value"
# Any other processing here ...
exit 1
fi
# Here your parameter is valid, proceed with processing ...
This works by having a value (here 1 but it could be anything else in that case) assigned to every valid parameter. So any invalid parameter will be null and the -z test will trigger.
Credits go to him.
Depending on how many different values you have, what about a case statement? It even allows for globbing.
case $1 in
(John) printf "Likes Yoko\n";;
(Paul) printf "Likes to write songs\n";;
(George) printf "Harrison\n";;
(Ringo) printf "Da drumma\n";;
(*) printf "Management, perhaps?\n";;
esac
On another note, if you can you should avoid unportable bashisms like the [[ test operator (and use [ if you can, e.g. if [ "$1" = "John" ]; then ...; fi.)

How to test for a character (or string) in a filename in dash

I have a string (a filename, actually) that I want to test whether they contain a character:
NEEDLE="-"
for file in mydirectory/*
do
if [NEEDLE IS FOUND IN STRING]
then
# do something
else
# do something else
fi
done
Basically this is the same question as String contains in Bash, except for dash instead of bash (ideally shell-neutral, which is usually the case with dash-code anyway)
The [[ operator does not exist in dash, therefore the answer to that question does not work.
use a case statement, no need to spawn an external program:
NEEDLE="-"
for file in mydirectory/*; do
case "$file" in
*"$NEEDLE"*) echo "do something" ;;
*) echo "do something else" ;;
esac
done
You can just use grep -q inside for loop:
NEEDLE="-"
for file in mydirectory/*; do
if grep -q "$NEEDLE" "$file"; then
echo "do something"
else
echo "do something else"
fi
done

Linux Regular Expression

I'm working with shell scripting in Linux. I want to check if the value of MAX_ARCHIVE_AGE is numeric or not. My code is like this:
MAX_ARCHIVE_AGE = "50"
expr="*[0-9]*"
if test -z "$MAX_ARCHIVE_AGE";
then
echo "MAX_ARCHIVE_AGE variable is missing or not initiated"
else
if [ "$MAX_ARCHIVE_AGE" != $expr ]
then
echo "$MAX_ARCHIVE_AGE is not a valid value"
fi
fi
I want to match the value of MAX_ARCHIVE_AGE with my expr. Please help.
For POSIX compatibility, look at case. I also find it more elegant than the corresponding if construct, but the syntax may seem a bit odd when you first see it.
case $MAX_ARCHIVE_AGE in
'' ) echo "empty" >&2 ;;
*[!0-9]* ) echo "not a number" >&2 ;;
esac
By the way, notice the redirection of error messages to standard error with >&2.
Your expr will match anything that contains any digits; it's better to check if it contains only digits, or conversely, to check if it contains any non-digits. To do that, you can write:
if ! [[ "$MAX_ARCHIVE_AGE" ]] ; then
echo "MAX_ARCHIVE_AGE is blank or uninitialized" >&2
elif [[ "$MAX_ARCHIVE_AGE" == *[^0-9]* ]] ; then
echo "$MAX_ARCHIVE_AGE is not a valid value" >&2
fi
Also, note that you would initialize MAX_ARCHIVE_AGE by writing e.g. MAX_ARCHIVE_AGE=50 (no spaces), not MAX_ARCHIVE_AGE = 50. The latter tries to run a program called MAX_ARCHIVE_AGE with the arguments = and 50.

What is the simplest way to check if a character is found within a variable in BASH?

I need to check if a variable contains a particular character, for use in an if-conditional in BASH, e.g.:
if [ "①" is in "$numbers" ]
then
echo "Found."
else
echo "Not found."
fi
If $numbers is "These are some numbers 1232", it returns "Not found.", but if "①" is found anywhere in the line, it returns "Found."
I have been using $numbers | grep -c ①, then checking if the output is greater than "0", but it seems there must be a simpler solution.
Right hand side of a comparison can be a pattern:
if [[ $numbers = *①* ]] ; then
As long as it's bash and doesn't need to be posix:
if [[ "$numbers" =~ ① ]]; then
echo "Found"
fi
For a posix solution, use a case statement in place of an if statement:
numbers="①"
case "$numbers" in
*①*) echo "Found it." ;;
*) echo "Not here." ;;
esac
This solution will work under dash which is the default shell (/bin/sh) for scripts under Debian-influenced distributions.

Correct way to check for a command line flag in bash

In the middle of a script, I want to check if a given flag was passed on the command line. The following does what I want but seems ugly:
if echo $* | grep -e "--flag" -q
then
echo ">>>> Running with flag"
else
echo ">>>> Running without flag"
fi
Is there a better way?
Note: I explicitly don't want to list all the flags in a switch/getopt. (In this case any such things would become half or more of the full script. Also the bodies of the if just set a set of vars)
An alternative to what you're doing:
if [[ $* == *--flag* ]]
See also BashFAQ/035.
Note: This will also match --flags-off since it's a simple substring check.
I typically see this done with a case statement. Here's an excerpt from the git-repack script:
while test $# != 0
do
case "$1" in
-n) no_update_info=t ;;
-a) all_into_one=t ;;
-A) all_into_one=t
unpack_unreachable=--unpack-unreachable ;;
-d) remove_redundant=t ;;
-q) GIT_QUIET=t ;;
-f) no_reuse=--no-reuse-object ;;
-l) local=--local ;;
--max-pack-size|--window|--window-memory|--depth)
extra="$extra $1=$2"; shift ;;
--) shift; break;;
*) usage ;;
esac
shift
done
Note that this allows you to check for both short and long flags. Other options are built up using the extra variable in this case.
you can take the straight-forward approach, and iterate over the arguments to test each of them for equality with a given parameter (e.g. -t, --therizinosaurus).
put it into a function:
has_param() {
local term="$1"
shift
for arg; do
if [[ $arg == "$term" ]]; then
return 0
fi
done
return 1
}
… and use it as a predicate in test expressions:
if has_param '-t' "$#"; then
echo "yay!"
fi
if ! has_param '-t' "$1" "$2" "$wat"; then
echo "nay..."
fi
if you want to reject empty arguments, add an exit point at the top of the loop body:
for arg; do
if [[ -z "$arg" ]]; then
return 2
fi
# ...
this is very readable, and will not give you false positives, like pattern matching or regex matching will.
it will also allow placing flags at arbitrary positions, for example, you can put -h at the end of the command line (not going into whether it's good or bad).
but, the more i thought about it, the more something bothered me.
with a function, you can take any implementation (e.g. getopts), and reuse it. encapsulation rulez!
but even with commands, this strength can become a flaw. if you'll be using it again and again, you'll be parsing all the arguments each time.
my tendency is to favor reuse, but i have to be aware of the implications. the opposed approach would be to parse these arguments once at the script top, as you dreaded, and avoid the repeated parsing.
you can still encapsulate that switch case, which can be as big as you decide (you don't have to list all the options).
You can use the getopt keyword in bash.
From http://aplawrence.com/Unix/getopts.html:
getopt
This is a standalone executable that has been around a long time.
Older versions lack the ability to handle quoted arguments (foo a "this
won't work" c) and the versions that can, do so clumsily. If you are
running a recent Linux version, your "getopt" can do that; SCO OSR5,
Mac OS X 10.2.6 and FreeBSD 4.4 has an older version that does not.
The simple use of "getopt" is shown in this mini-script:
#!/bin/bash
echo "Before getopt"
for i
do
echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
echo "-->$i"
done
I've made small changes to the answer of Eliran Malka:
This function can evaluate different parameter synonyms, like "-q" and "--quick". Also, it does not use return 0/1 but an echo to return a non-null value when the parameter is found:
function has_param() {
local terms="$1"
shift
for term in $terms; do
for arg; do
if [[ $arg == "$term" ]]; then
echo "yes"
fi
done
done
}
# Same usage:
# Assign result to a variable.
FLAG_QUICK=$(has_param "-q --quick" "$#") # "yes" or ""
# Test in a condition using the nonzero-length-test to detect "yes" response.
if [[ -n $(has_param "-h --help" "$#") ]]; then;
echo "Need help?"
fi
# Check, is a flag is NOT set by using the zero-length test.
if [[ -z $(has_param "-f --flag" "$#") ]]; then
echo "FLAG NOT SET"
fi
The modification of Dennis Williamson's answer with additional example for a argument in the short form.
if [[ \ $*\ == *\ --flag\ * ]] || [[ \ $*\ == *\ -f\ * ]]
It solves the problem of false positive matching --flags-off and even --another--flag (more popular such case for an one-dashed arguments: --one-more-flag for *-f*).
\ (backslash + space) means space for expressions inside [[ ]]. Putting spaces around $* allows to be sure that the arguments contacts neither line's start nor line's end, they contacts only spaces. And now the target flag surrounded by spaces can be searched in the line with arguments.
if [ "$1" == "-n" ]; then
echo "Flag set";
fi
Here is a variation on the most voted answer that won't pick up false positives
if [[ " $* " == *" -r "* ]]; then
Not an alternative, but an improvement, though.
if echo $* | grep -e "\b--flag\b" -q
Looking for word boundaries will make sure to really get the option --flag and neither --flagstaff nor --not-really--flag

Resources