With the python --version as input in the terminal, I'd like to confirm (also in the shell in the form of shell programming) whether any python versions installed are 3 or higher. For example,
Python 3.2.1
Would yield True or some form of confirmation, where as
python2.2p1 or Python 2.1.4 would yield False.
I've tried using regex through sed or grep, but can't get it.
Note, this is a different matter from what some think to be a duplicate of, as I'm asking the shell programming code that can automate confirmation of python3 or higher being installed, not a way to manually check it (just entering python --version).
With the python --version as input in the terminal, I'd like to confirm (also in the shell in the form of shell programming) whether any python versions installed are 3 or higher.
If the python command launches any version of cpython, then the output of python --version (to its stderr, not stdout) will have the form:
Python X.Y.Z
You can test that in a shell script like so:
if python --version 2>&1 | grep -q '^Python 3\.'; then
# It's python 3 ...
else
# It's not python 3; maybe not installed
fi
Note that the output you originally presented in the question indicated that Python was not installed at all. The above approach will execute the else branch in that event. Note also that it is possible for Python to be installed with a different name -- for example, on the system where I am typing this, python is Python 2.7.5, but Python 3.4.8 is available via the command python3. You could extend the above to test some possible alternative names for Python 3, but you cannot safely, reliably, or efficiently perform an exhaustive test for whether Python 3 is installed at all, under any name or path.
The easiest way to do this in Bash is using sort -V.
To solve the problem of comparing version strings in general in bash, here is an simple compare() function that accepts two arguments a and b and returns true if a <= b. And then some unit tests showing its validity for various Python version inputs:
# test.sh
compare() {
local a=$1 ; local b=$2
[ "$( (echo "$a" ; echo "$b") | sort -V | head -1)" == "$a" ]
}
test22p1() {
compare "2.2p1" "3.0.0"
assertTrue "$?"
}
test214() {
compare "2.1.4" "3.0.0"
assertTrue "$?"
}
test300() {
compare "3.0.0" "3.0.0"
assertTrue "$?"
}
test372() {
compare "3.7.2" "3.0.0"
assertFalse "$?"
}
. shunit2
Output:
▶ bash test.sh
test22p1
test214
test300
test372
Ran 4 tests.
OK
(Those unit tests assume that shunit2 is installed of course.)
About the function:
It just echoes $a then $b on two separate lines, pipes into sort -V, and takes the head. If the head is equal to the left-hand side, true is returned ; otherwise if it is equal to the right-hand side, false is returned.
In your question you mentioned you really want to know if Python 3 or greater is installed, so you could modify it and have something like this instead:
python3_installed() {
local a b
a=$(python --version 2>&1 | perl -pe 's/python *//i') ; b=3
[ "$( (echo "$a" ; echo "$b") | sort -V | head -1)" == "$b" ]
}
This function will compute the actual version of Python installed, and compare it to "3" using sort -V and return false unless 3 or greater is installed.
Note use of Perl there to do a case-insensitive regex search and replace. (Not all sed's have the case-insensitive ability.)
The great thing about doing it this way is you can then have readable code when you call it, like:
if python3_installed ; then
# yes it is!
else
# ...
fi
Finally, according to the docs for sort -V (from the BSD manual; POSIX doesn't specify a -V option, but most sorts seem to have it):
`-V, --version-sort`
Sort version numbers. The input lines are treated as file names in form PREFIX VERSION SUFFIX, where SUFFIX matches the regular expression (.([A-Za-z~][A-Za-z0-9~]*)?)*. The files are compared by their prefixes and versions (leading zeros are ignored in version numbers, see example below). If an input string does not match the pattern, then it is compared using the byte compare function. All string comparisons are performed in C locale, the locale environment setting is ignored.
Here is sample awk script to do the job:
echo $(python --version) | awk 'BEGIN{found="false"}/python-3/{found="true"}END{print found}'
Related
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
So I've done some Google searching and this is something that has very little knowledge out there. What would be an effective and foolproof way of detecting whether X11 or Wayland is in use, preferrably at compile-time and with CMake? I need to apply this to a C++ project of mine.
The accepted answer is very inaccurate and dangerous. It just runs loginctl to dump a large list of user-sessions and greps every line with a username or other string that matches the current user's name, which can lead to false positives and multiple matching lines. Calling whoami is also wasteful. So it's harmful, and inaccurate.
Here's a much better way to get the user's session details, by querying your exact username's details and grabbing their 1st session scope's id.
This is a Bash/ZSH-compatible one-liner solution:
if [ "$(loginctl show-session $(loginctl user-status $USER | grep -E -m 1 'session-[0-9]+\.scope' | sed -E 's/^.*?session-([0-9]+)\.scope.*$/\1/') -p Type | grep -ic "wayland")" -ge 1 ]; then
echo "Wayland!"
else
echo "X11"
fi
I really wish that loginctl had a "list all sessions just for a specific user", but it doesn't, so we have to resort to these tricks. At least my trick is a LOT more robust and should always work!
I assume you want to evaluate the display server during compile time, when calling CMake, instead of for every compilation. That's how CMake works and hot it should be used. One downside is, that you have to re-run CMake for every changed display server.
There is currently no default way to detect the running display server. Similar, there is no default code snippet to evaluate the display server by CMake. Just pick one way of detecting the display server that manually works for you or your environment respectively.
Call this code from CMake and store the result in a variable and use it for your C++ code.
For example loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type works for me. The resulting CMake check is
execute_process(
"loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type"
OUTPUT_VARIABLE result_display_server)
if ("${resulting_display_server}" EQUALS "Type=x11")
set(display_server_x11 TRUE)
else()
set(display_server_x11 FALSE)
endif()
Probably you have to fiddle around with the condition and check for Type=wayland or similar to get it properly working in your environment.
You can use display_server_x11 and write it into a config.h file to use it within C++ code.
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).
I wish to use git log --oneline | wc -l to provide the revision number for sed substitution in the following Lua file (simplified) during each commit using a script and $GIT/hooks:
-- settings.lua
local module = {}
-- major, minor, build, and revision number
module.version = "10.0.0.0"
return module
I'm testing from the command-line with this:
sed -E 's/(version\s*=\s*\"\d+\.\d+\.\d+\.)/\1foo"/' settings.lua
All sed outputs is the following verbatim copy of the input:
-- settings.lua
local module = {}
-- major, minor, build, and revision number
module.version = "10.0.0.0"
return module
I must be totally misunderstanding the things I'm ready about sed and its purpose in life. Coming from a 20 years background C/C++ and in now C# I'm amazed I can't get my head around this. I know that pattern matches!
My sed understanding is that it will match version = "10.0.0.0" and then change the entire matched string to version = "10.0.0.foo" which once done I can simple " and a > settings.au to replace the original file with the substation in place.
As usual I've drifted way too far off track thinking something was going to be simple but wasn't and just because I want it. I was really enjoying the Lua part too.
In sed, \d wouldn't work the way you expect it to work. Use [0-9] or [[:digit:]] instead of \d
EDIT
Another way to do it:
sed -i.bak '/module.version/s/"$/foo"/' File
AMD$ cat File
-- settings.lua
local module = {}
-- major, minor, build, and revision number
module.version = "10.0.0.0foo"
return module
For lines matching module.version, substitute the last " with foo".
The above command will edit the file inplace, keeping a backup.
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 [[.