Bash non greedy replace - bash

Say I have a string
foo='teledunet&file=rotana_aflam&provider=rtmp&autostart=true&'
If I wanted to cut out the file parameter I could do
$ echo ${foo/&file=[^&]*}
teledunet
but as you can see the search is being greedy and taking the rest of the string. Can it be made to replace non-greedy?

$ echo ${foo/&file*([^&])}
teledunet&provider=rtmp&autostart=true&
You may have to shopt -s extglob to enable extended glob syntax first.

Related

How to list and sort pictures with just one number in its name?

How can I list all jpg with a number as a name in bash on a mac? I have a folder with several pictures all with numbers in its name.
However, I just want to have those pictures listed and sorted in bash with just 1 number in its name, with the following layout: 1.jpg or 30.jpg, but not 1-1.jpg or 30-1-2.jpg etc.
I tried ls $* | sort -V, but it gives me all pictures.
How can I achieve the desired result?
You don't need any third party tool for this, just enable bash extended globbing (if not enabled by default). The nullglob option allows filename patterns which match no files to expand to a null string, rather than themselves
shopt -s extglob nullglob
If you are sure, there can't be filenames containing other alphanumeric characters, then to exclude filenames containing -, all you need to do is
printf '%s\n' !(*-*).jpg
or be specific to match only filenames with digits as
printf '%s\n' [[:digit:]]!(*-*).jpg
Or wrap this over in a sub-shell to avoid setting the glob options persistent in your interactive shell.
( shopt -s extglob nullglob ; printf '%s\n' [[:digit:]]!(*-*).jpg ; ) |
sort -V
As to why your attempt didn't work, ls $* can never work, because $* is a special bash shell variable created by concatenating all the command line arguments passed and joined by the value of IFS. Were you trying to pass * as the argument and process $* inside a function/script and sort on the list returned?
Use Bash's built-in extended globbing patterns.
shopt -s extglob
ls +([[:digit:]]).{{[jJ][pP],[pP][nN]}[gG],{[gG],[tT]}[iI][fF]?([fF])} | sort -n
or with case insensitive globbing:
shopt -s extglob nocaseglob
ls +([[:digit:]]).{{jp,pn}g,{g,t}if?(f)} | sort -n
Or if you look only for lowercase .jpg:
shopt -s extglob
ls +([[:digit:]]).jpg | sort -n

one-or-more pattern in parameter substitution in 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

getops $OPTARG is empty if flag value contains brackets

When I pass a flag containing [...] to my bash script, getops gives me an empty string when I try to grab the value with $OPTARG.
shopt -s nullglob
while getopts ":f:" opt; do
case $opt in
f)
str=$OPTARG
;;
esac
done
echo ${str}
Running the script:
$ script.sh -f [0.0.0.0]
<blank line>
How can I get the original value back inside the script?
Short summary: Double-quote your variable references. And use shellcheck.net.
Long explanation: When you use a variable without double-quotes around it (e.g. echo ${str}), the shell tries to split its value into words, and expand anything that looks like a wildcard expression into a list of matching files. In the case of [0.0.0.0], the brackets make it a wildcard expression that'll match either the character "0" or "." (equivalent to [0.]). If you had a file named "0", it would expand to that string. With no matching file(s), it's normally left unexpanded, but with the nullglob set it expands to ... null.
Turning off nullglob solves the problem if there are no matching files, but isn't really the right way do it. I remember (but can't find right now) a question we had about a script that failed on one particular computer, and it turned out the reason was that one computer happened to have a file that matched a bracket expression in an unquoted variable's value.
The right solution is to put double-quotes around the variable reference. This tells the shell to skip word splitting and wildcard expansion. Here's an interactive example:
$ str='[0.0.0.0]' # Quotes aren't actually needed here, but they don't hurt
$ echo $str # This works without nullglob or a matching file
[0.0.0.0]
$ shopt -s nullglob
$ echo $str # This fails because of nullglob
$ shopt -u nullglob
$ touch 0
$ echo $str # This fails because of a matching file
0
$ echo "$str" # This just works, no matter whether file(s) match and/or nullglob is set
[0.0.0.0]
So in your script, simply change the last line to:
echo "${str}"
Note that double-quotes are not required in either case $opt in or str=$OPTARG because variables in those specific contexts aren't subject to word splitting or wildcard expansion. But IMO keeping track of which contexts it's safe to leave the double-quotes off is more hassle than it's worth, and you should just double-quote 'em all.
BTW, shellcheck.net is good at spotting common mistakes like this; I recommend feeding your scripts through it, since this is probably not the only place you have this problem.
Assuming that shopt -s nullglob is needed in the bigger script.
You can temporary disable shopt -s nullglob using shopt -u nullglob
shopt -s nullglob
shopt -u nullglob
while getopts ":f:" opt; do
case $opt in
f)
str=$OPTARG
;;
esac
done
echo ${str}
shopt -s nullglob

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")

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