Calling sed in a qmake script - bash

Here is my problem:
I want have my qmake script detect my opencv version and save the result in the CONFIG variable. I need the result to have this form : "opencv20","opencv21","opencv22",etc.
I know that I can use the system() function to call bash commands and wanted to use something like this :
CONFIG += opencv$$system(pkg-config --modversion opencv | cut -d. -f'1,2' | sed 's/\.//g')
It works fine in my terminal, but qmake gives me "opencv2." when I try to print the output. The outputs of pkg-config and cut commands alone are correct so I assume the sed call is confusing qmake somehow... any hints ?

system() commands are executed in a subshell. That's why you have to escape your strings:
CONFIG += opencv$$system(pkg-config --modversion opencv | cut -d . -f \'1,2\' | sed \'s/\.//g\')

Related

MacOS catalina Makefile not processing shell commands correctly that work in CLI

TL;DR:
This does not work in a Makefile in MacOS (catalina):
grab_setup_rule != cat $(all_makefiles_except_current) | grep -h -E '.+-setup:.*##' | awk '{ print $1 }'
but on executed on the command line, with $(all_makefiles_except_current) replaced with the correct paths, it works.
In depth explanation
Hey,
I have the following code in a Makefile:
BASEPATH = ..
repos = project1 project2
possible_includes = $(foreach repo,$(repos), $(BASEPATH)/$(repo)/Makefile)
-include $(possible_includes)
all_makefiles_except_current := $(wordlist 2,$(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
grab_setup_rule != cat $(all_makefiles_except_current) | grep -h -E '.+-setup:.*##' | awk '{ print $1 }'
What this does is include all the Makefiles specified in the repos variable, get a list of all included files, set in all_makefiles_except_current, and then try to grab all the targets from those Makefiles that end with -setup.
This is an example of what is in the included Makefiles:
projectName-targetX: ### desc
code
projectName-setup: ## desc
code
<.. more targets ..>
This is a self-documenting makefile.
If I run the shell commands:
cat ../project1/Makefile | grep -h -E '.+-setup:.*##' | awk '{ print $1 }'
NOTE: on windows and linux the grep regex has to be without single quotes, ex.: grep -h -E .+-setup:.*##
on the command line, IT WORKS, I get the targets that I want and this will work on Windows (msys2), on Linux (ubuntu) and on MacOS (catalina).
But on the Makefile the grab_setup_rule will always be empty in MacOS. Seems it doesn't get processed correctly.
What am I doing wrong in the grab_setup_rule on MacOS?
The != operator was added in GNU make 4.0. It is not available in older releases.
Apple is scared of the GPLv3 license and refuses to ship any GNU software using that license, so they will never provide a version of GNU make newer than 3.81 (released in 2006). They have similarly old and broken versions of other GNU software including their standard interactive shell, bash.
You can either download the GNU make source code and build it yourself, or use brew or similar to get a newer version, or else use the := $(shell ...) method of invoking a shell command and assigning it to a variable which works in GNU make 3.81 as well.

Cygwin Command Substitution not Working

I am trying to trouble shoot a problem I am seeing when running bash commands in Cygwin.
I am trying to assign the CLang version from a text file to a variable. If I run this in Cygwin:
$ (sed -n 1p "$CLANGC2_VERSION_FILE" | sed 's/\s//g')
I get this output (which is exactly what I want):
14.10.25903
Now, if I try and assign this to a variable it doesn't work. Here is what I am trying:
$ CLANGC2_VERSION=$(sed -n 1p "$CLANGC2_VERSION_FILE" | sed 's/\s//g')
but when I inspect or print the variable, it is empty.
What am I doing wrong?
Turns out that there is a known 'Big List of Dodgy Apps' (BLODA) which can interfere with Cygwin and bash.
The discussion I found is here: https://cygwin.com/ml/cygwin/2017-07/msg00197.html
The BLODA list is here: https://cygwin.com/faq/faq.html#faq.using.bloda
Turns out my AntiVirus is on the list.
I've removed the AV and now the commands work. There must be some low-level stuff going with the AV that causes it to fail.
You can use backticks to get the desired results.
CLANGC2_VERSION=`(sed -n 1p "$CLANGC2_VERSION_FILE" | sed 's/\s//g')`

Bash statement meaning

I'm working on a project, and it's being run by an autoscript. The script has the following line:
./executable ./dev | grep -i "GET.*index.*200" > ./dev/logs/log1
I have my code writing to stdout, but it never gets written to log1. If I change it though and remove the grep command, it writes just fine. Any help would be appreciated, as I seemingly don't understand grep as well as I should.
You might try to redirect std output in your script "executable" using commands:
exec > ./dev/logs/log1
exec 2> ./dev/logs/errlog1
So, now not need to use ">" in the line
./executable ./dev | grep -i "GET.*index.*200"
Also I recommend you to use only absolute paths in scripts.
ps. [offtop] I can't write comments yet (not enough reputation).

properly handling shell escaping from Python using os.system

I'm having trouble properly escaping calls to the shell from within Python, using the os.system command. I'm trying to do the equivalent of:
$ cat test | sort --stable -t $'\t' -k1,1
from within Python, passing that to the shell.
I tried:
import os
cmd = "cat %s | sort --stable -t $'\\t' -k1,1" %("test")
os.system(cmd)
but I get the error:
sort: multi-character tab `$\\t'
although it works correctly from the shell. I tried to escape the \t by adding an extra slash in Python, but I must be missing something else. Any idea how this can be fixed?
thanks.
os.system doesn't execute commands in a normal bash environment like you would expect. You can work around it by simply calling bash yourself:
import os
cmd = """/bin/bash -c "cat %s | sort --stable -t $'\t' -k1,1" """ % "test"
os.system(cmd)
But you should be aware that os.system has been marked as deprecated, and will be removed in future versions of python. You can future-proof your code by using subprocess's convenience method call that mimics os.system's behavior:
import subprocess
cmd = """/bin/bash -c "cat %s | sort --stable -t $'\t' -k1,1" """ % "test"
subprocess.call(cmd, shell=True)
There are more ways to make that call with the subprocess module if you are interested:
http://docs.python.org/library/subprocess.html#module-subprocess
First, you should avoid useless-use-of-cat: http://google.com/search?q=uuoc.
Secondly, are you sure that your sort command not understand backslash-t? This should work:
sort --stable -t'\t' -k1,1 test
It should also work just fine from Python:
os.system("sort --stable -t'\\t' -k1,1 test")
# or
os.system(r"sort --stable -t'\t' -k1,1 test")
Finally, if you switch to subprocess (recommended), avoid using shell=True:
subprocess.call(["sort", "--stable", "-t\t", "-k1,1", "test"])

bash: shortest way to get n-th column of output

Let's say that during your workday you repeatedly encounter the following form of columnized output from some command in bash (in my case from executing svn st in my Rails working directory):
? changes.patch
M app/models/superman.rb
A app/models/superwoman.rb
in order to work with the output of your command - in this case the filenames - some sort of parsing is required so that the second column can be used as input for the next command.
What I've been doing is to use awk to get at the second column, e.g. when I want to remove all files (not that that's a typical usecase :), I would do:
svn st | awk '{print $2}' | xargs rm
Since I type this a lot, a natural question is: is there a shorter (thus cooler) way of accomplishing this in bash?
NOTE:
What I am asking is essentially a shell command question even though my concrete example is on my svn workflow. If you feel that workflow is silly and suggest an alternative approach, I probably won't vote you down, but others might, since the question here is really how to get the n-th column command output in bash, in the shortest manner possible. Thanks :)
You can use cut to access the second field:
cut -f2
Edit:
Sorry, didn't realise that SVN doesn't use tabs in its output, so that's a bit useless. You can tailor cut to the output but it's a bit fragile - something like cut -c 10- would work, but the exact value will depend on your setup.
Another option is something like: sed 's/.\s\+//'
To accomplish the same thing as:
svn st | awk '{print $2}' | xargs rm
using only bash you can use:
svn st | while read a b; do rm "$b"; done
Granted, it's not shorter, but it's a bit more efficient and it handles whitespace in your filenames correctly.
I found myself in the same situation and ended up adding these aliases to my .profile file:
alias c1="awk '{print \$1}'"
alias c2="awk '{print \$2}'"
alias c3="awk '{print \$3}'"
alias c4="awk '{print \$4}'"
alias c5="awk '{print \$5}'"
alias c6="awk '{print \$6}'"
alias c7="awk '{print \$7}'"
alias c8="awk '{print \$8}'"
alias c9="awk '{print \$9}'"
Which allows me to write things like this:
svn st | c2 | xargs rm
Try the zsh. It supports suffix alias, so you can define X in your .zshrc to be
alias -g X="| cut -d' ' -f2"
then you can do:
cat file X
You can take it one step further and define it for the nth column:
alias -g X2="| cut -d' ' -f2"
alias -g X1="| cut -d' ' -f1"
alias -g X3="| cut -d' ' -f3"
which will output the nth column of file "file". You can do this for grep output or less output, too. This is very handy and a killer feature of the zsh.
You can go one step further and define D to be:
alias -g D="|xargs rm"
Now you can type:
cat file X1 D
to delete all files mentioned in the first column of file "file".
If you know the bash, the zsh is not much of a change except for some new features.
HTH Chris
Because you seem to be unfamiliar with scripts, here is an example.
#!/bin/sh
# usage: svn st | x 2 | xargs rm
col=$1
shift
awk -v col="$col" '{print $col}' "${#--}"
If you save this in ~/bin/x and make sure ~/bin is in your PATH (now that is something you can and should put in your .bashrc) you have the shortest possible command for generally extracting column n; x n.
The script should do proper error checking and bail if invoked with a non-numeric argument or the incorrect number of arguments, etc; but expanding on this bare-bones essential version will be in unit 102.
Maybe you will want to extend the script to allow a different column delimiter. Awk by default parses input into fields on whitespace; to use a different delimiter, use -F ':' where : is the new delimiter. Implementing this as an option to the script makes it slightly longer, so I'm leaving that as an exercise for the reader.
Usage
Given a file file:
1 2 3
4 5 6
You can either pass it via stdin (using a useless cat merely as a placeholder for something more useful);
$ cat file | sh script.sh 2
2
5
Or provide it as an argument to the script:
$ sh script.sh 2 file
2
5
Here, sh script.sh is assuming that the script is saved as script.sh in the current directory; if you save it with a more useful name somewhere in your PATH and mark it executable, as in the instructions above, obviously use the useful name instead (and no sh).
It looks like you already have a solution. To make things easier, why not just put your command in a bash script (with a short name) and just run that instead of typing out that 'long' command every time?
If you are ok with manually selecting the column, you could be very fast using pick:
svn st | pick | xargs rm
Just go to any cell of the 2nd column, press c and then hit enter
Note, that file path does not have to be in second column of svn st output. For example if you modify file, and modify it's property, it will be 3rd column.
See possible output examples in:
svn help st
Example output:
M wc/bar.c
A + wc/qax.c
I suggest to cut first 8 characters by:
svn st | cut -c8- | while read FILE; do echo whatever with "$FILE"; done
If you want to be 100% sure, and deal with fancy filenames with white space at the end for example, you need to parse xml output:
svn st --xml | grep -o 'path=".*"' | sed 's/^path="//; s/"$//'
Of course you may want to use some real XML parser instead of grep/sed.

Resources