Conditional Characters in Shell Globbing - Bash/Zsh - bash

I'm trying to get a case statement to match one of four inputs in a Bash/Zsh shell:
v
-v
version
--version
I'm looking for this in the below case statement:
case "$1" in
?(--)version|?(-)v)
# do stuff
;;
esac
And I find that this isn't working. From what I've read, ?(pattern) is how to match 0 or one occurrences of a pattern.
I did have it working by matching the case
--version|version|-v|v)
But it would be nice to have something neater, plus, it's a learning experience!
I imagine this is probably down to me not escaping things properly, but I also tried encompassing my original string in double quotes ("), yet I got no output again.
Any advice?

To provide an answer to this, the best solution (if insisting on using some form of globbing) is:
(|--)version|(|-)v )
But as rightly pointed out by #kvantour, it's much better to just stick with the simpler:
--version|version|-v|v )

Related

Using pattern in Shell Parameter Expansion

I am reading a page and trying to extract some data from it. I am interested in using bash and after going through few links, i came to know that 'Shell Parameter Expansion' might help however, i am finding difficulty using it in my script. I know that using sed might be easier but just for my knowledge i want to know how can i achieve this in bash.
shopt -s extglob
str='My work</u><br /><span style="color: rgb(34,34,34);"></span><span>abc-X7-27ABC | </span><span style="color: rgb(34,34,34);">build'
echo "${str//<.*>/|}"
I want my output to be like this: My work|abc-X7-27ABC |build
I thought of checking whether it accepts only word instead of pattern and it seems to be working with words.
For instance,
echo "${str//span style/|}" works but
echo "${str//span.*style/|}" doesn't
On the other hand, i saw in one of the link that it does accept pattern. I am confused why it's not working with the patern i am using above.
How to make sed do non-greedy match?
(User konsolebox's solution)
One mistake you're making is by mixing shell globbing and regex. In shell glob dot is taken literally as dot character not as 0 or more of any character.
If you try this code instead:
echo "${str//<*>/|}"
then it will print:
My work|build
This is not an answer, so much as a demonstration of why pattern-matching is not recommended for this kind of HTML editing. I attempted the following.
shopt -s extglob
set +H # Turn off history expansion, if necessary, to allow the !(...) pattern
echo ${str//+(<+(!(>))>)/|}
First: it didn't work, even for a simpler string like str='My work</u><br />bob<foo>build'. Second, for the string in the original question, it appeared to lock up the shell; I suspect such a complex pattern triggers exponential backtracking.
Here's how it's intended to work:
!(>) is any thing other than a single >
+(!(>)) is one or more non-> characters.
<+(!(>))> is one or more non-> characters enclosed in < and >
+(<+(!(>))>) is one or more groups of <...>-enclosed non->s.
My theory is that since !(>) can match a multi-character string as well as a single character, there is a ton of backtracking required.

variable substitution removing quotes

I seem to have some difficulty getting what I want to work. Basically, I have a series of variables that are assigned strings with some quotes and \ characters. I want to remove the quotes to embed them inside a json doc, since json hates quotes using python dump methods.
I figured it would be easy. Just determine how to remove the characters easy and then write a simple for loop for the variable substitution, well it didn't work that way.
Here is what I want to do.
There is a variable called "MESSAGE23", it contains the following "com.centrify.tokend.cac", I want to strip out the quotes, which to me is easy, a simple echo $opt | sed "s/\"//g". When I do this from the command line:
$> MESSAGE23="com."apple".cacng.tokend is present"
$> MESSAGE23=`echo $MESSAGE23 | sed "s/\"//g"`
$> com.apple.cacng.tokend is present
This works. I get the properly formatted string.
When I then try to throw this into a loop, all hell breaks loose.
for i to {1..25}; do
MESSAGE$i=`echo $MESSAGE$i | sed "s/\"//g"`
done
This doesn't work (either it throws a bunch of indexes out or nothing), and I'm pretty sure I just don't know enough about arg or eval or other bash substitution variables.
But basically I want to do this for another set of variables with the same problems, where I strip out the quotes and incidentally the "\" too.
Any help would be greatly appreciated.
You can't do that. You could make it work using eval, but that introduces another level of quoting you have to worry about. Is there some reason you can't use an array?
MESSAGE=("this is MESSAGE[0]" "this is MESSAGE[1]")
MESSAGE[2]="I can add more, too!"
for (( i=0; i<${#MESSAGE[#]}; ++i )); do
echo "${MESSAGE[i]}"
done
Otherwise you need something like this:
eval 'echo "$MESSAGE'"$i"'"'
and it just gets worse from there.
First, a couple of preliminary problems: MESSAGE23="com."apple".cacng.tokend is present" will not embed double-quotes in the variable value, use MESSAGE23="com.\"apple\".cacng.tokend is present" or MESSAGE23='com."apple".cacng.tokend is present' instead. Second, you should almost always put double-quotes around variable expansions (e.g. echo "$MESSAGE23") to prevent parsing oddities.
Now, the real problems: the shell doesn't allow variable substitution on the left side of an assignment (i.e. MESSAGE$i=something won't work). Fortunately, it does allow this in a declare statement, so you can use that instead. Also, when the sees $MESSAGE$i it replaces it will the value of $MESSAGE followed by the value of $i; for this you need to use indirect expansion (`${!metavariable}').
for i in {1..25}; do
varname="MESSAGE$i"
declare $varname="$(echo "${!varname}" | tr -d '"')"
done
(Note that I also used tr instead of sed, but that's just my personal preference.)
(Also, note that #Mark Reed's suggestion of an array is really the better way to do this sort of thing.)

Why do I get this error using {1..9} in zsh?

I run the following code
zgrep -c compinit /usr/share/man/man{1..9}/zsh*
I get
zsh: no matches found: /usr/share/man/man2/zsh*
This is strange, since the following works
echo Masi{1..9}/masi
This suggests me that the problem may be a bug in Zsh.
Is the above a bug in Zsh for {1..9}?
It's not a bug, and it is working inside words fine. The trouble you're having here is that {1..9} is not a wildcard expression like * is; as your echo example shows, it's an iterative expansion. So your zgrep example is exactly the same as if you had typed each alternate version into the command line, and then since there are no man pages starting with zsh in man2, it errors out. (It's erroring out on a failure to find a match, not anything intrinsically related to your brace sequence expansion.)
If you did this, on the other hand:
zgrep -c compinit /usr/share/man/man[1-9]/zsh*
you'd get the results you expect, because [1-9] is a normal wildcard expression.
In zsh, if you want to use ranges in filenames, zle offers <1-n> on any real names it can expand on. That is to say:
$ touch a0b a1b a5b a7b
$ print a<0-100>b
And then hit <Tab> right after the final b would leave you with print a0b a1b a5b a7b expanded on the line.
For all other intents and purposes - perhaps full range requirements, non-file and scripting use - I'd express this using the rather succinct idiomatic zsh loop as:
for n ({1..50}); do print $n; done
Will allow you process the whole sequence range of numbers 1 to 50 :) after which you can do all sorts of useful things with, such as a file collection that doesn't exist yet:
arr=($(for n ({1..50}); do print /my/path/file$n.txt; done)) && print $arr[33]

Search and replace in Shell

I am writing a shell (bash) script and I'm trying to figure out an easy way to accomplish a simple task.
I have some string in a variable.
I don't know if this is relevant, but it can contain spaces, newlines, because actually this string is the content of a whole text file.
I want to replace the last occurence of a certain substring with something else.
Perhaps I could use a regexp for that, but there are two moments that confuse me:
I need to match from the end, not from the start
the substring that I want to scan for is fixed, not variable.
for truncating at the start: ${var#pattern}
truncating at the end ${var%pattern}
${var/pattern/repl} for general replacement
the patterns are 'filename' style expansion, and the last one can be prefixed with # or % to match only at the start or end (respectively)
it's all in the (long) bash manpage. check the "Parameter Expansion" chapter.
amn expression like this
s/match string here$/new string/
should do the trick - s is for sustitute, / break up the command, and the $ is the end of line marker. You can try this in vi to see if it does what you need.
I would look up the man pages for awk or sed.
Javier's answer is shell specific and won't work in all shells.
The sed answers that MrTelly and epochwolf alluded to are incomplete and should look something like this:
MyString="stuff ttto be edittted"
NewString=`echo $MyString | sed -e 's/\(.*\)ttt\(.*\)/\1xxx\2/'`
The reason this works without having to use the $ to mark the end is that the first '.*' is greedy and will attempt to gather up as much as possible while allowing the rest of the regular expression to be true.
This sed command should work fine in any shell context used.
Usually when I get stuck with Sed I use this page,
http://sed.sourceforge.net/sed1line.txt

Bash: select a previous command that matches a pattern

I know about the bash history navigation with the Up and Down arrows.
I would like a lazy way to select a previous command that matches some regex (that is shorter than the whole command, so it takes less time to be typed).
Is it possible with bash?
If not, do other shells have such a feature?
You can always use CTRL-R to search your history backward, and type some part of the previous command. Hitting CTRL-R again (after the first hit) repeats your query (jumps to the next match if any).
Personally I use this for regex search (as regex searching is not possible yet (AFAIK)):
# search (using perl regexp syntax) your entire history
function histgrep()
{
grep -P $# ~/.bash_history
}
Edit:
For searching most recent history items via that function, see this (on setting $PROMPT_COMMAND ).
Zsolt, avoid hard-coded filenames, use the HISTFILE variable instead, with a fallback if you're really paranoid: ${HISTFILE:-~/.bash_history} ;-)
Any why grepping directly through the history file?! You'd lose the history number, which is necessary to replicate the command (e.g. !33 to execute again the 33th entry from your history) without having to copy&paste grep's output.
Please keep in mind that using that kind of $# expansions may fail at various (epic) levels. For instance, an argument beginning with "-" (histgrep -h) will usually hang, or shoot yourself in the foot. Indeed, this basic example may be worked around easily, following the classic "--" way of separating arguments from options, but the discussion has no ending, remembering that the arguments to be provided to that hack would be regular expressions. ;-)
Oh, and isn't histgrep somehow a too verbose choice? h, i, Tab, g, Tab :)
IMHO I'd stick to using ^R, falling back to history | grep ... whenever necessary.
Anyway, for the sake of the example, I'd (lazily) rewrite this little helper as:
function hgrep() { history | grep -P -- "$*"; }
See my answer here
Example:
$echo happy
happy
$!?pp?
happy
I prefere this solution since one can see all the contents of the whole history in addition to search by regex, and the regex-type is according to the very good regex engine of vim:
vim -u /root/.vimrc -M + <( history )
This I have given in this my post:
https://unix.stackexchange.com/questions/718828/search-in-whole-bash-history-list-with-full-featured-regex-engine
Regards
Anton Wessel

Resources