one-or-more pattern in parameter substitution in bash - bash

I wonder whether there is a way to specify an one-or-more modifier for a character class in a parameter substitution pattern in bash, similar to a regex: i.e. [a-z]+. It seems like that for instance to remove all trailing whitespaces from a variable I would need to use ${fn##*[[:space:]]} (would only work for fn=" test" but not for fn=" test a") however I was hoping for something like ${fn##[[:space:]]+}. Is there a special syntax to specify regex instead of the patterns? Where is the pattern format specified?

Using extglob, you can do this:
shopt -s extglob
fn=" test a"
echo "${fn##+([[:space:]])}"
test a
Here glob expression +([[:space:]]) matches 1 or more whitespace characters.

You cannot use regular expressions in parameter expansions like that. However, the extended pattern syntax enabled by extglob is equivalent in power.
$ fn=" test a"
$ echo "$fn"
test a
$ shopt -s extglob
$ echo "${fn##*([[:space:]])}"
test a

Related

glob pattern doesn't expand inside file in zsh

I am trying to exclude a directory from a glob.
This works at the command line:
$ export exclude=BigDir
$ for d in ^$exclude/ ; do echo "$d" ; done
SmallDir/
SmallerDir/
$
But in a file it doesn't work at all
#!/bin/zsh
exclude=BigDir
for d in ^$exclude/ ; do echo "$d" ; done
Running ./test or however I saved it prints the literal string
^BigDir/
How do I get it to correctly expand in the script file?
You are incorrectly using the glob characters ? used by the shell and the regular expression constructs ^, $. The for loop in your example can not undergo a regex match to exclude the directory provided, since it undergoes only pathname expansion (aka. glob expansion)
Unless you let know the shell to treat ^ and $ as special by enabling extended glob options extglob in bash and extendedglob in zsh, you cannot achieve what you wanted to do.
So you probably just need
setopt extendedglob
print -rl ^BigDir*
meaning print anything except the the filenames matching with BigDir.

Remove specified string pattern(s) from a string in bash

I found a good answer that explains how to remove a specified pattern from a string variable. In this case, to remove 'foo' we use the following:
string="fooSTUFF"
string="${string#foo}"
However, I would like to add the "OR" functionality that would be able to remove 'foo' OR 'boo' in the cases when my string starts with any of them, and leave the string as is, if it does not start with 'foo' or 'boo'. So, the modified script should look something like that:
string="fooSTUFF"
string="${string#(foo OR boo)}"
How could this be properly implemented?
If you have set the extglob (extended glob) shell option with
shopt -s extglob
Then you can write:
string="${string##(foo|boo)}"
The extended patterns are documented in the bash manual; they take the form:
?(pattern-list): Matches zero or one occurrence of the given patterns.
*(pattern-list): Matches zero or more occurrences of the given patterns.
+(pattern-list): Matches one or more occurrences of the given patterns.
#(pattern-list): Matches one of the given patterns.
!(pattern-list): Matches anything except one of the given patterns.
In all cases, pattern-list is a list of patterns separated by |
You need an extended glob pattern for that (enabled with shopt -s extglob):
$ str1=fooSTUFF
$ str2=booSTUFF
$ str3=barSTUFF
$ echo "${str1##(foo|boo)}"
STUFF
$ echo "${str2##(foo|boo)}"
STUFF
$ echo "${str3##(foo|boo)}"
barSTUFF
The #(pat1|pat2) matches one of the patterns separated by |.
#(pat1|pat2) is the general solution for your question (multiple patterns); in some simple cases, you can get away without extended globs:
echo "${str#[fb]oo}"
would work for your specific example, too.
You can use:
string=$(echo $string | tr -d "foo|boo")

Can you match numbers using shell parameter expansion?

I'm trying to remove some numbers from end of a string using parameter expansion, like:
export ENV=dev12
echo ${ENV##[0-9]+}
But it doesn't work and I can't find anything on google on how to do this? Anyone know?
Parameter expansion uses glob syntax, not the more canonical regex syntax that grep and other tools use. You also have to use %% since ## is for prefixes.
There's no plain glob equivalent for what you want to do, but since you're using bash you can can enable extglob and use +([0-9]):
shopt -s extglob
ENV=dev12
echo ${ENV%%+([0-9])}
Using BASH regex you can do:
str='dev12'
[[ $str =~ ^(.*[^[:digit:]])[[:digit:]]+$ ]] && echo "${BASH_REMATCH[1]}"
dev

bash export expression without expanding?

I want to do the following
export LOGS=//server/log_files/2014_*/server_{1,2,3}
so I can do something like
grep 'Exception' $LOGS/log.txt
I tried alias as well, but I can't get it to not expand.
How could I do this?
Without export, right hand side of an assignment doesn't go through path nor brace expansion.
With export, though, brace expansion is performed. You can prevent it by quoting the value:
export LOGS='//server/log_files/2014_*/server_{1,2,3}'
If you want to use such a value, though, you have to use eval:
eval grep 'Exception' $LOGS/log.txt
You're in the case where you want extended globs. That's the cleanest and the most semantically correct here, as you want to match filenames. As I'm overly pedantic, I'd argue that brace expansions are not the right tool for your task.
# This defines a string that will glob
# No pathname expansions are performed at this step
logs_glob='//server/log_files/2014_*/server_#(1|2|3)'
# You need to activate extended globs with extglob
# To have a failure when no files match the glob, you need failglob
shopt -s failglob extglob
# Unquoted variable $logs_glob, as pathname expansion is desirable
grep 'Exception' $logs_glob
Some will argue that with glob techniques you can't properly handle spaces in names. In fact, you have two ways: either use ? as a wildcard (this will match any character, hence spaces in particular) or use the character class [[:space:]]. This character class will match any space (regular spaces, newlines, tabs, etc.)
Another technique is to use arrays, still with extended globs. I'd argue that this is cleaner.
shopt -s extglob nullglob
# This will populate array with all matching filenames.
# If no matches, array is empty (since we shopted nullglob)
logs_array=( //server/log_files/2014_*/server_#(1|2|3) )
# Before you launch you command with the array, make sure it's not empty:
if ((${#logs_array[#]}!=0)); then
# Observe the quotes for the expansion of the array
grep 'Exception' "${logs_array[#]}"
fi

how to match more than one word in bash

I'd like list files with the name pattern like [max|min].txt, so execute
ls [max|min].txt in bash shell, but it doesn't work, and the error message I got is:
ls: cannot access [max: No such file or directory
so what's the right way to do this job?
Square brackets are for character matching, and vertical bars are for pipes. You're looking for brace expansion.
ls {max,min}.txt
Bash has a shell option called extglob that you can enable with the command shopt -s extglob. This will allow you to use the pattern format #(pattern-list) where pattern-list is a pipe separated list of patterns. It will match against filenames and will exclude any pattern that does not match a filename, just like the [abc] range expression. Bash also has brace expansion, but this does not appear to be what you are asking for, as brace expansion does not match against filenames or expand like wildcards or range expressions do.
$ shopt -s extglob
$ touch max.txt min.txt
$ echo #(max|min).txt
max.txt min.txt
$ echo #(min|mid|max).txt
max.txt min.txt
$ echo {min,mid,max}.txt
min.txt mid.txt max.txt
A couple of things to note about the sequence of commands above:
echo #(mid|min|max).txt does not output mid.txt because there is no file that matches.
echo #(min|mid|max).txt re-orders the output to be sorted, in the same manner as a wildcard expansion.
echo {min,mid,max}.txt is brace expansion and outputs all elements in the order given.

Resources