Conditional statement using rexep with bash - regex

I want to use the "conditional regexp" structure from bash, present since the third version of bash (circa 2004).
It's supposed to go like this:
if [[ $string =~ $regexp ]]; then
#do smthg
else
#do somthg else
fi
So here's my code, following this structure, and its role is to check if the name contained in SSID is presend in the output from iw dev wlan0 link :
if [[ $(iw dev wlan0 link) =~ $SSID+ ]]; then
#do sthming
else
echo "wrong network"
fi
For some reason that i can't decipher, this statement works pretty well if I run it right into the bash shell, like
if [[ $(iw dev wlan0 link) =~ $SSID+ ]]; then echo found; else echo not found; fi
But if i run it inside the script its contained, it'll spit out:
scripts/ssidchecker.sh: 22: [[: not found
22 being the line of the "fi" keyword. The strangest thing is that it will always execute the code contained in the "else" statement
Is "not found" meant to indicate me that the regexp dind't find anything in that string? Is it a real error message?

Starting Note: Answer created with input from comments in question, especially in response of the last comment.
First, what is [[? It is a shell keyword.
samveen#maverick:~$ type [[
[[ is a shell keyword
This means that [[ is an internal keyword of bash, not a command and thus will not work with other shells. Thus, your error output
scripts/ssidchecker.sh: 22: [[: not found
means that the shell you're using is probably
Not bash
A version of bash older than 2.02 ([[ introduced in Bash-2.02)
Given that 2.02 is a really really old version (pre Y2K), all this just points to the fact that the shell you're using to run the script is probably not /bin/bash, instead probably being /bin/sh which is the most common path used for Bourne shell scripts shebang (the #!) line.
Please change that to /bin/bash or explicitly run bash scripts/ssidchecker.sh and you're good to go.
As to why the else section was always executed, the [[ command not being found is the same as a failure (non-zero return value), from the viewpoint of if. Thus the else section is executed.
samveen#maverick:~$ /bin/whosyourdaddy
-bash: /bin/whosyourdaddy: No such file or directory
samveen#maverick:~$ echo $?
127
As a side note on portability, the bash hackers wiki also says the following about the [[ keyword:
Amongst the major "POSIX-shell superset languages" (for lack of a
better term) which do have [[, the test expression compound command is
one of the very most portable non-POSIX features. Aside from the =~
operator, almost every major feature is consistent between Ksh88,
Ksh93, mksh, Zsh, and Bash. Ksh93 also adds a large number of unique
pattern matching features not supported by other shells including
support for several different regex dialects, which are invoked using
a different syntax from Bash's =~, though =~ is still supported by ksh
and defaults to ERE.
Thus your script will probably fail on the =~ even if the shell supports [[, in case the shell isn't bash but supports [[.

Related

Store regex pattern in a variable in bash script

I have the following bash script code:
GOAL="${1:-help}"
TARGET="${2}"
MODULES_LIST="app|tester"
echo "-> Running $TARGET..."
MODULES_LIST_PATTERN = "^($MODULES_LIST)$"
if [[ "$TARGET" =~ $MODULES_LIST_PATTERN ]]; then
run_${TARGET}
else
print_error "You must include an existing module: {$MODULES_LIST}"
exit 1
fi
As you can see, I have a MODULES_LIST variable where I store the modules supported by the application, then I create a regex pattern MODULES_LIST_PATTERN containing the value of the previous var, and use it to check if the provided parameter matches any of the modules. However, it is not working as expected, since when I run ./myscript.sh run app it prints ERROR] You must include an existing module: {app|tester}.
Could someone tell me the proper way of doing this?
My fault... the problem is MODULES_LIST_PATTERN = "^($MODULES_LIST)$", you can't have whitespaces surrounding the equals sign. After fixing that, it works as expected.

rpm conditional with substring?

I have a spec file that uses a conditional like
%if "%{pkgname}" = "wobble"
Requires: extra-thing
....
%endif
and now need to treat wobble-thing, wobble-otherthing and any other wobble* as satisfying the same conditon. Easy, you'd think.
I can't find any way to do it without giving up on the awfulness that is spec files and preprocessing the file.
Unfortunately preprocessing the file won't fly in this context, it'll upturn a whole build chain that expects to be able to just rpmbuild the spec.
There's lots of undocumented magic in rpm and rpmbuild, like the ${error:blah} and %{expand:..othermacros...} and %global stuff. And even simple relational operations like %if %{value} < 42 don't seem to actually be documented anywhere.
Does anyone know of a string-prefix or string-infix pattern matching operator?
I'm looking for the spec equivalent of bash's if [[ "${VAR}" == *"substring"* ]]; then construct.
Edit: To be clear, the reason I'm not just using shell conditionals is that I need to affect rpm metadata. I thought it was obvious that I'd use shell if if that was an option. I've edited to show more clearly above.
Edit: To help other people find this, this is about string pattern matching in rpm. Complex conditionals in rpm. Conditional sections in spec files. String prefix, infix or suffix operators and tests in rpmbuild.
You cannot use regexp or wildcards. But you can use "or".
%if "%{pkgname}" == "wobble" || "%{pkgname}" == "wobble-thing"
..
%endif
or you can do the evaluation in the shell
%global yourmacro %(/usr/bin/perl ...%{pkgname}... )
where /usr/bin/perl ... can be any script and yourmacro is set to value of stdout of this script.
Actually, you can do it without needing Lua, and you can use exactly the if [[ ... ]] construct in BASH that you mentioned wanting to use in your original question.
To illustrate how, let's look at the header section of the specfile for the tmux RPM I use. It must build unmodified on any host running RHEL/CentOS versions 6, 7, and 8 (and/or any other rebuilds, like Oracle's). This is what I have:
%global name tmux
%global version 3.1b
%global release 1%{?dist}
%global _hardened_build 1
Summary: A terminal multiplexer
Name: %{name}
Version: %{version}
Release: %{release}
# Mostly ISC-licensed, but some files in compat/ are 2-/3-clause BSD.
License: ISC/BSD
URL: https://tmux.github.io/
Source0: https://github.com/tmux/%{name}/releases/download/%{version}/%{name}-%{version}.tar.gz
Source1: bash_completion_tmux.sh
BuildRequires: %(/bin/bash -fc 'if [[ %{name} == tmux* ]]; then echo make ; else echo nomake ; fi') %(/bin/bash -fc 'if [[ %{name} == wobble* ]]; then echo wobble ; fi')
BuildRequires: gcc, ncurses-devel, %{expand:%(/bin/bash -c 'if [[ %{?rhel}%{!?rhel:9} -le 6 ]]; then echo libevent2-devel ; else echo libevent-devel ; fi')}
Requires: %{expand:%(/bin/bash -c 'if [[ %{?rhel}%{!?rhel:9} -le 6 ]]; then echo libevent2 ; else echo libevent ; fi')} >= 2.0
The tricky part with this RPM is that RHEL6 supplies the libevent package, but it's version 1.4.13, which is too old for tmux to build against successfully. (It requires libevent 2.x.) The good news, though, is that RHEL6 actually has both versions! The libevent2 RPM has version 2.0.21, which is recent enough to work for tmux. But I can't just list libevent2-devel as my build dependency; that package is not available for RHEL 7 or 8 because their default libevent RPM is version 2 already. Nor can I list libevent-devel as the build dependency because, while the libevent and libevent2 packages can be installed side-by-side, the libevent-devel and libevent2-devel RPMs conflict.
As you can see in my specfile above, my solution to this challenge is to use RPM macros to inject either the string libevent-devel or libevent2-devel into the BuildRequires: header value based on the BASH-based comparison of the value of the %{rhel} macro (to which /etc/rpm/macros.dist assigns the value of 6, 7, or 8 on RHEL 6, 7, or 8, respectively). For RHEL6 (and prior, theoretically...RHEL5 is dead, and RHEL4 and earlier are super-duper-dead!), the latter value is used whereas the former value is used for RHEL7 and RHEL8.
I also added a couple contrived string-matching macro invocations just to illustrate that it works correctly. My actual specfile contains neither the first BuildRequires: line nor the final Requires: line (because it's unnecessary/redundant); I added them specifically for this exercise to demonstrate that the conditionals work for both build-time and run-time dependencies.
How do I know they worked? I checked:
$ rpm -qp --qf '[%|SOURCERPM?{%25{=NEVRA}}:{%21{=NEVR}.src}|.rpm: %{REQUIREFLAGS:deptype}: %{REQUIRENEVRS}\n]' tmux-3.1b-1.el6.x86_64.rpm tmux-3.1b-1.el6.src.rpm tmux-3.1b-1.el8.x86_64.rpm tmux-3.1b-1.el8.src.rpm | fgrep 'manual:'
tmux-3.1b-1.el6.x86_64.rpm: manual: libevent2 >= 2.0
tmux-3.1b-1.el6.src.rpm: manual: make
tmux-3.1b-1.el6.src.rpm: manual: gcc
tmux-3.1b-1.el6.src.rpm: manual: ncurses-devel
tmux-3.1b-1.el6.src.rpm: manual: libevent2-devel
tmux-3.1b-1.el8.x86_64.rpm: manual: libevent >= 2.0
tmux-3.1b-1.el8.src.rpm: manual: gcc
tmux-3.1b-1.el8.src.rpm: manual: libevent-devel
tmux-3.1b-1.el8.src.rpm: manual: make
tmux-3.1b-1.el8.src.rpm: manual: ncurses-devel
Note that the correct libevent2 >= 2.0 dependency (with the 2 on the end) shows up in the RHEL6 RPM; the RHEL8 RPM, on the other hand, shows libevent >= 2.0 (without the 2 on the end), again exactly as it should. Additionally, a build dependency on make shows up in both SRPMs, and both nomake and wobble are absent, proving that the pattern match conditionals on tmux* and wobble* worked exactly as they should.
You can indeed use Lua scripting, though it requires some odd incantations. Here's how to add a starts_with function-like macro to your rpm spec file that you can use in %if conditions.
# Define the Lua starts_with function we want to expose
%{lua:
function starts_with(str, start)
return str:sub(1, #start) == start
end
}
# Define the rpm parametric macro starts_with(str,prefix) that
# calls Lua and maps "false"=>"0" and "true"=>"1"
#
# Note that we need to inject the parameters %1 and %2 to a
# string-quoted version of the Lua macro, then expand the whole
# thing.
#
%define starts_with(str,prefix) (%{expand:%%{lua:print(starts_with(%1, %2) and "1" or "0")}})
# Finally we can use the parametric macro
#
%if %{starts_with "wobble-boo" "wobble"}
Requires: wobble
%endif
What happens here is that:
%{starts_with "wobble-boo", "wobble}
expands to
%{expand:%%{lua:print(starts_with("wobble-boo", "wobble") and "1" or "0")}}
which expands to
%{lua:print(starts_with("wobble-boo", "wobble") and "1" or "0")}
which executes the Lua function starts_with, which tests if the left-anchore substring of "str" that's the same length as "start" is equal to "start". If that's true it returns "1"; if it's false it returns "0". That's because rpm doesn't recognise false as false.
So what we're doing here is calling a Lua function from a parametric rpm macro and adapting the return value.
Nifty. Painful that rpm needs this kind of hack for a simple task like this, and that it's almost totally undocumented. But nifty.
Here is a way to do it in the spec using %define.
%global original_string SomeLongStringHere
%global matching_string LongString
%global nomatch_string NoMatchHere
%define matchTwoStrings(n:) \
string_one=%1 \
string_two=%2 \
if [[ "${string_one}" = *"${string_two}"* ]]; then \
echo "It matches" \
else \
echo "It doesn't match" \
fi \
%{nil}
# Then somewhere later in the .spec, e.g., I used the %post section
%post
%matchTwoStrings "%{original_string}" "%{matching_string}"
%matchTwoStrings "%{original_string}" "%{nomatch_string}"
Because this is in the %post section, it will print out during the .rpm installation.
It matches
It doesn't match

Using regex in git shell when checking out multiple branches

If I have the following git shell command:
for branch in `git branch -a | grep remotes | grep -v master | sed 's/^.*old\/\(.*\)/\1/g'`; do git branch --track $branch remotes/old/$branch; done
This checks out every single remote branch that exists on the old remote and tracks them using the same name that they have on that remote. However, what if I wanted to slightly change the name that the local branches are checked out has?
What if I have the following remote branches:
release/1.2.1.0
release/1.2.1.1
And I want to check them out under the same parent folder release but I only want the last 3 digits in the version number. So I want my local branches to be:
release/2.1.0
release/2.1.1
I have a simple javascript regex that matches the last 3 digits of the version string: (?:\d\.)(\d.*)
This uses a non-matching group to toss out the first digit followed by the period. The question is, how do I apply that regex to the $branch variable in the git shell bash script above?
First, avoid git branch for loops like this. The correct tool here is git for-each-ref, which is designed to work with scripting languages (git branch is aimed at users and the output format may change in the future, for instance).
To loop over all remote-tracking branches, simply tell for-each-ref to scan the remote-tracking branch namespace. Since you want, more specifically, the remote named old, you can do that very easily by adding /old as well:
git for-each-ref refs/remotes/old
The output here defaults to a triple of objectname objecttype refname. We only care about the refname part (and we can use the :short modifier to drop refs/remotes/ as well, if we like, although we still need to drop the old/ too so we could get away without the modifier). Thus we want to include --format=%(refname:short).
Moving on to bash, bash has built-in regular expression support. Its RE syntax is not quite the same, though, so your existing RE must change. Here is one that probably works for your needs:
bash$ x=1.2.3.4
bash$ [[ $x =~ ([0-9]\.)([0-9.]+) ]] && echo ${BASH_REMATCH[2]}
2.3.4
(There is a bit of subtlety here: using $x changes the way the =~ match applies, which in our case is probably good. As an old school Unix person I generally prefer using expr myself, but in this case I might resort to doing this in Python, which has Perl-style REs, and Javascript/ECMAscript REs are modeled on Perl's. But all that is more or less irrelevant. The most important is that this RE is slightly sub-par as a version number matcher. For instance, it matches strings like "1.3..6". We're safe in that these are invalid branch names—double dots are verboten since they would conflict with the set subtraction syntax in gitrevisions—but it's generally a bit sloppy; with some work we could come up with a tighter expression. It also fails to match revisions starting with two or more digits, but your original RE did as well, so I left that in on purpose.)
Reading in a loop in shell, using -r is generally wise (see Etan Reisner's comment), although in this case we could omit it safely since git controls branch names. I will use it in the example just for form's sake.
Putting these all together:
warn() {
echo "warning: $#" 1>&2
}
# Given an input name release/\d\.(\d|\.)+, make
# a local branch named release/\2 (more or less).
make_local_release_branch() {
local relnum newname
relnum=${1#old/release/}
[[ $relnum =~ ([0-9]\.)([0-9.]+) ]] || {
warn "remote-tracking branch $1 does not conform to name style, ignored"
return
}
newname=release/${BASH_REMATCH[2]}
git rev-parse -q --verify refs/heads/$newname >/dev/null && {
warn "branch $newname already exists, remote-tracking branch $1 ignored"
return
}
git branch --track $branch $1
}
git for-each-ref --format='%(refname:short)' refs/remotes/old |
while read -r rmtbranch; do
case $rmtbranch in
old/release/[0-9]*) make_local_release_branch "$rmtbranch";;
*) warn "skipping remote branch $rmtbranch -- not old/release/[digit]";;
esac
done
(this whole thing is entirely untested).

Why does FINDSTR behave differently in powershell and cmd?

The following command pipes the output of echo to findstr and tries to match a regular expression with it. I use it to check if the echoed line only consists of (one or more) digits:
echo 123 | findstr /r /c:"^[0-9][0-9]*$"
The expected output of findstr is 123, which means that the expression could be matched with this string. The output is correct when I execute the command with powershell.exe.
Executing the command in cmd.exe however does not give a match. It only outputs an empty line and sets %ERRORLEVEL% to 1, which means that no match was found.
What causes the different behavior? Is there a way to make this command run correctly on cmd as well?
My OS is Windows 7 Professional, 64 Bit.
In Powershell the command echoes the string 123 to the pipeline and that matches your regular expression.
In cmd, your command echos 123<space> to the pipeline. The trailing space isn't allowed for in your regular expression so you don't get a match.
Try:
echo 123| findstr /r /c:"^[0-9][0-9]*$"
and it will work just fine. Or just switch entirely to Powershell and stop having to worry about the vagaries of cmd.exe.
Edit:
Yes, cmd and powershell handle parameters very differently.
With cmd all programs are passed a simple text command line. The processing that cmd performs is pretty minimal: it will terminate the command at | or &, removes i/o redirection and will substitute in any variables. Also of course it identifies the command and executes it. Any argument processing is done by the command itself, so a command can choose whether spaces separate arguments or what " characters mean. Mostly commands have a fairly common interpretation of these things but they can just do their own thing with the string they were given. echo does it's own thing.
Powershell on the other hand has a complex syntax for arguments. All of the argument parsing is done by Powershell. The parsed arguments are then passed to Powershell functions or cmdlets as a sequence of .Net objects: that means you aren't limited to just passing simple strings around. If the command turns out not to be a powershell command and runs externally it will attempt to convert the objects into a string and puts quotes round any arguments that have a space. Sometimes the conversion can be a bit confusing, but it does mean that something like this:
echo (1+1)
will echo 2 in Powershell where cmd would just echo the input string.
It is worth always remembering that with Powershell you are working with objects, so for example:
PS C:\> echo Today is (get-date)
Today
is
17 April 2014 20:03:15
PS C:\> echo "Today is $(get-date)"
Today is 04/17/2014 20:03:20
In the first case echo gets 3 objects, two strings and a date. It outputs each object on a separate line (and a blank line when the type changes). In the second case it gets a single object which is a string (and unlike the cmd echo it never sees the quote marks).

Strict Regular Expression String Comparison in Bash Script

I'm attempting to write a simple Bash script that will help me verify some Windows registry settings. All the data is within folders (which is the computer's hostname), and at the path stated in the script. The end goal is to verify the hosts that have the value incorrectly set, based on registry key value at the end of the line.
For reference of the script, parameter one is the registry key I would like to look for, parameter two is the value it should be. If $control matches what is on the end of $value's string then it should output the machine name which is the variable $FOLDER
list=$(ls)
regkey=$1
control=$2
for FOLDER in $list
do
value=$($FOLDER/policies/Effective-Security-policy.txt | grep "$regkey")
if [[ "$value" =~ $control ]] ;
then
echo $FOLDER
else
continue
fi
done
However, I can't get it to do a strict compare, because there is also a registry key named RestrictAnonymousSAM and it will list out values that are incorrect.
Here are some of the lines from within the text file, I need to be able to differentiate between the two, so the returned values are for that particular registry key:
MACHINE\System\CurrentControlSet\Control\Lsa\RestrictAnonymous=4,0
MACHINE\System\CurrentControlSet\Control\Lsa\RestrictAnonymousSAM=4,1
If I understand correctly what you're trying to do, I think you should write:
regkey="$1"
control="$2"
for FOLDER in * ; do
file="$FOLDER/policies/Effective-Security-policy.txt"
if iconv -s -f utf-16 -t utf-8 "$file" \
| grep -q '^[^=]*\\'"$regkey=$control$"
then
echo "$FOLDER"
fi
done
grep -q searches the file for lines matching the pattern, but does not print them out. This makes it well-suited to use in if-tests, since grep returns 0 (success/true) if it finds a match and 1 (error/false) if it does not.
(Important note: the above assumes that $regkey and $control can't contain any metacharacters that grep might treat specially. If they can, then this becomes trickier.)