Bash - Comparing a string to an array that contains wildcards? - bash

I have an array of possible file extensions, which contains some wild cards e.g.:
FILETYPES=("DBG" "MSG" "OUT" "output*.txt")
I also have a list of files, which I am grabbing the file extension from. I then need to compare the extension with the array of file extensions.
I have tried:
if [[ ${EXTENSION} =~ "${FILETYPES[*]}" ]]; then
echo "file found"
fi
if [[ ${EXTENSION} == "${FILETYPES[*]}" ]]; then
echo "file found"
fi
and
if [[ ${EXTENSION} =~ "${FILETYPES[*]}" ]]; then
echo "file found"
fi
But to no avail
I tried:
if [[ "${FILETYPES[*]}" =~ ${EXTENSION} ]]; then
echo "file found"
fi
However, it ended up comparing "txt" to "output*.txt" and concluding it was a match.

FILETYPES=("DBG" "MSG" "OUT" "output*.txt") First of all, avoid ALL_CAPS variable names except if these are meant as global environment variables.
"output*.txt": is ok as a globing pattern, for bash test [[ $variable == output*.txt ]] for example. But for Regex matching it needs a different syntax like [[ $variable =~ output.*\.txt ]]
"${FILETYPES[*]}": Expanding this array into a single_string was mostly a good approach, but it needs clever use of the IFS environment variable to help it expands into a Regex. Something like IFS='|' regex_fragment="(${array[*]})", so that each array entry will be expanded, separated by a pipe | and enclosed in parenthesis as (entry1|entry2|...).
Here is an implementation you could use:
textscript.sh
#!/usr/bin/env bash
extensions_regexes=("DBG" "MSG" "OUT" "output.*\.txt")
# Expands the extensions regexes into a proper regex string
IFS='|' regex=".*\.(${extensions_regexes[*]})"
# Prints the regex for debug purposes
printf %s\\n "$regex"
# Iterate all filenames passed as argument to the script
for filename; do
# Compare the filename with the regex
if [[ $filename =~ $regex ]]; then
printf 'file found: %s \n' "$filename"
fi
done
Sample usage:
$ touch foobar.MSG foobar.output.txt
$ bash testscript.sh *
.*\.(DBG|MSG|OUT|output.*\.txt)
file found: foobar.MSG
file found: foobar.output.txt

You cannot directly compare a string with an array. Would you please try something like:
filetypes=("DBG" "MSG" "OUT" "output*.txt")
extension="MSG" # example
match=0
for type in "${filetypes[#]}"; do
if [[ $extension = $type ]]; then
match=1
break
fi
done
echo "$match"
You can save looping with regex:
pat="^(DBG|MSG|OUT|output.*\.txt)$"
extension="output_foo.txt" # example
match=0
if [[ $extension =~ $pat ]]; then
match=1
fi
echo "$match"
Please note the expressions of regex differ from wildcards for globbing.
As a side note, we conventionally do not use uppercases for user variables to avoid conflicts with system variables.

Related

How to check for space in a variable in bash?

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.

Patter matching variable and string

bash script
Hi! I would like to make a bash script that contolli if the content of a var variable does pattern matching with the string ending with ABC?
You can use the bash builtins:
# with glob patterns
if [[ $var == *ABC ]]; then echo "$var ends with ABC"; fi
# with regular expression
if [[ $var =~ ABC$ ]]; then echo "$var ends with ABC"; fi
You are almost there
var="hellowordABC"
echo $var | grep ".*ABC$"
Or using builtin conditions
[[ $var =~ ABC$ ]] && echo "var ends with ABC"

Does glob work differently with [[ ]] vs [ ] in Bash?

I have the file $HOME/foo/foo.mov
if [[ -f $HOME/**/*.mov ]]; then
echo "file is there" else
echo "file is not there"
fi
echoes "file is not there". whereas,
if [ -f $HOME/**/*.mov ]]; then
echo "file is there" else
echo "file is not there"
fi
echoes "file there".
Why is the difference between [[ ]] and [ ]?
Arguments to the [ command are subject to pathname expansion; the words used inside [[ ... ]] are not.
If you are getting file is there, I suspect you have exactly one file that matches the pattern. Otherwise, you would have too many arguments for -f, or the pattern would be treated as a literal string that does not name an existing file.

String contains correct version of java

I am new to Bash and having a problem checking a variable contains a string
Works:
foo="abc def ghi"
if [[ "$foo" =~ "def" ]]; then
echo "Match!"
fi
Does not work (issue i'm having):
javaVersion="$(java -version)"
if [[ "$javaVersion" =~ "1.8.0_74" ]]; then
echo "Match!"
fi
I have manually checked the variable contains the string 1.8.0_74.
The problem is java -version prints the information to stderr(2) stream instead of stdout(1). You need to capture both of them as 2>&1 which literally means write the standard error output stream also to standard output stream.
javaVersion="$(java -version 2>&1)"
if [[ "$javaVersion" =~ "1.8.0_74" ]]; then
echo "Match!"
fi
will work as expected.
Also you don't a a regex operator for this comparison, a simple glob comparison using the test operator [[ would suffice,
if [[ "$javaVersion" == *"1.8.0_74"* ]]; then
echo "Match!"
fi

Pass script arguments as a pattern

I have a bash script that requires a glob expression as a parameter. However I am having trouble using inputs as globs i.e say my input is
Shell_script '*.c'
and my code is iterating through an array of files and filtering them through pattern matching. In this case files which do not have the .c extension. (In this example, the first input could be any pattern otherwise)
count=${#array[#]}
for (( q = 0; q < count; q++ ));
do
if [[ ${array[q]} == $1 ]]; then
:
else unset array[q]
fi
done
.....
Any ideas?
Matching array contents against a glob is entirely possible:
#!/bin/bash
# this array has noncontiguous indexes to demonstrate a potential bug in the original code
array=( [0]="hello.c" [3]="cruel.txt" [5]="world.c" )
glob=$1
for idx in "${!array[#]}"; do
val=${array[$idx]}
if [[ $val = $glob ]]; then
echo "File $val matches glob expression $glob" >&2
else
echo "File $val does not match glob expression $glob; removing" >&2
unset array[$idx]
fi
done
Similarly, you can expand a glob against filesystem contents, though you'll want to clear IFS first to avoid string-splitting:
# here, the expectation is that your script would be invoked as: ./yourscript '*.c'
IFS=
for f in $1; do
[[ -e $f || -L $f ]] || { echo "No file matching $f found" >&2; }
echo "Iterating over file $f"
done
That said, in general, this is extremely unidiomatic, as opposed to letting the calling shell expand the glob before your script is started, and reading the list of matched files off your argument vector. Thus:
# written this way, your script can just be called ./yourscript *.c
for f; do
[[ -e $f || -L $f ]] || { echo "No file matching $f found" >&2; }
echo "Iterating over file $f"
done
You can loop over your list of files like this. If you run your script as
./test.sh "*.c". Then inside your script you can do:
for file in $1
do
#use your file
done

Resources