In bash I can do something like this in order to check if a program exists:
if type -P vim > /dev/null; then
echo "vim installed"
else
echo "vim not installed"
fi
I would like to do the same thing in a Makefile.
In details I would like to choose "python3" if installed, else "python" (2). My Makefile looks like this:
PYTHON = python
TSCRIPT = test/test_psutil.py
test:
$(PYTHON) $(TSCRIPT)
Is there anything I can do to use a conditional around that PYTHON = python line? I understand Makefiles can be told to use bash syntax somehow (SHELL:=/bin/bash?) but I'm no expert.
The easiest thing is probably to use $(shell) to figure out if python3 is callable:
ifeq ($(shell which python3),)
PYTHON = python
else
PYTHON = python3
endif
$(shell which python 3) runs which python3 in a shell and expands to the output of that command. That is the path of python3 if it is available, and otherwise it is empty. This can be used in a conditional.
Addendum: About the portability concerns in the comments: the reason that $(shell type -P python3) does not work is that GNU make attempts to optimize away the shell call and fork/exec itself, which does not work with a shell builtin. I found this out from here. If your /bin/sh knows type -P, then
# note the semicolon -------v
ifeq ($(shell type -P python3;),)
works. My /bin/sh is dash, though, so that didn't work for me (it complained about -P not being a valid command). What did work was
ifeq ($(shell type python3;),)
because dash's type sends the error message about unavailable commands to stderr, not stdout (so the $(shell) expands to the empty string). If you can depend on which, I think doing that is the cleanest way. If you can depend on bash, then
ifeq ($(shell bash -c 'type -P python3'),)
also works. Alternatively,
SHELL = bash
ifeq ($(shell type -P python3;),)
has the same effect. If none of those are an option, desperate measures like #MadScientist's answer become attractive.
Or, if all else fails, you can resort to searching the path yourself:
PYTHON = $(shell IFS=:; for dir in $$PATH; do if test -f "$$dir/python3" && test -x "$$dir/python3"; then echo python3; exit 0; fi; done; echo python)
This is lifted from the way autoconf's AC_CHECK_PROG is implemented. I'm not sure whether I'd want this, though.
If you wanted to be more portable you can try invoking the command itself to see if it works or not:
PYTHON := $(shell python3 --version >/dev/null 2>&1 && echo python3 || echo python)
PYTHON := $(shell type -P python3 || echo "python")
You could use command -v:
PYTHON := $(shell command -v python3 2> /dev/null || echo python)
In Bash, command is a builtin command.
The example above is for GNU Make. Other Make programs may have a different syntax for running shell commands.
Related
By default when I type python in terminal it goes to python using the 2.7.x version. In oldest versions of macOS once I typed 'alias pyhton=python3' it changed forever and every time I typed python it goes to python version 3.
But in macOS Catalina, I need to type the statement every time I open the terminal.
Any suggestion?
First up, confirm what your default "python" links to, so you can reference it, and ensure that which and the shell agree:
user:~> which python
/usr/bin/python
user:~> type python
python is /usr/bin/python
user:~> ls -la /usr/bin/python
lrwxrwxrwx 1 root root 7 Oct 8 13:26 /usr/bin/python -> python2
Now you can either add an alias to override this in your shell... .
Open your ~/.bash_profile file for bash or ~/.zshrc file for zsh (look here for historical reasons behind the files used) as suggested by shahaf, and add a line with the alias — for example, quick method:
echo "alias python=/usr/bin/python3" >> ~/.bash_profile
echo "alias python=/usr/bin/python3" >> ~/.zshrc
The new alias will be set for the next shell you start, or, open a new Terminal window and source the profile file to make it active. Eg. in bash:
source ~/.bash_profile
Or, change the symlink to point by default to python3, and remember the change (I use a simple toggle script, else any install of a missing python2 package can result in complaints about the configure script, which uses the python symlink directly):
#!/bin/bash
TOGGLE=$HOME/.python3Active
if [ ! -e $TOGGLE ]; then
touch $TOGGLE
sudo ln -fs python3 /usr/bin/python
ls -la /usr/bin/python
echo "Press any key to continue..."
read
else
rm $TOGGLE
sudo ln -fs python2 /usr/bin/python
ls -la /usr/bin/python
echo "Press any key to continue..."
read
fi
Catalina now uses zsh as the default rather than Bash.
To verify which shell you are using type echo $0 in the terminal
Add alias python='python3' to $HOME/.zshrc
you will have to edit the terminal's profile file, usually reside under ~/.profile
add the alias line there, this files get loaded when a terminal session is started and export environment variables and methods so they will be accessible in that session
I suggest to use a more robust and powerful terminal enhancement like Z-Shell
I have a python script which I run on localhost and development in command line with argument, sth as python script.py development - on development and python script.py localhost - on localhost.
Now I want to run this script - when I running script /bin/bash sh,
so I want to run this script from /bin/.bash script.
I added in headers in sh script: #!/usr/bin/env python.
In what way I can achieve this?
do
if [ $1 == "local" ]; then
python script.py $1
elif [ $1 == "development" ]; then
python script.py $1
What I can do to improve this script?
Since $1 already contains what you want, the conditional is unnecessary.
If your script is a Bash script, you should put #!/bin/bash (or your local equivalent) in the shebang line. However, this particular script uses no Bash features, and so might usefully be coded to run POSIX sh instead.
#!/bin/sh
case $1 in
local|development) ;;
*) echo "Syntax: $0 local|development" >&2; exit 2;;
esac
exec python script.py "$1"
A more useful approach is to configure your local system to run the script directly with ./script.py or similar, and let the script itself take care of parsing its command-line arguments. How exactly to do that depends on your precise environment, but on most U*x-like systems, you would put #!/usr/bin/env python as the first line of script.py itself, and chmod +x the file.
I assume this is what you wanted...
#!/bin/bash
if [ ! "$#" ]; then
echo "Usage: $1 (local|development) "
exit
fi
if [ "$1" == "local" ]; then
python script.py "$1"
echo "$1"
elif
[ "$1" == "development" ]; then
python script.py "$1"
echo "$1"
fi
Save the bash code above into a file named let's say script.sh. The make it executable: chmod +x script.sh. Then run it:
./script.sh
If no argument is specified, the script will just print an info about how to use it.
./script.sh local - executes python script.py local
./script.sh development - executes python script.py development
You can comment the lines with echo, they were left there just for debugging purposes (add a # in front of the echo lines to comment them).
My application is writen in python 3, and I work in a virtualenv. On my cluster, there is hdp (hortonworks) installed and some scripts require python 2. Those script have #!/usr/bin/env python in the header, but it links to my python 3 installation because my virtualenv is activated. How to solve this ? I can't modify hdp source for obvious reasons.
Modifying Your Virtualenv
If you want your virtualenv to always be ignored with a #!/usr/bin/env python shebang (but not with a #!/usr/bin/env python3 shebang), there's a big-hammer approach that prevents the python entry in the PATH added by the virtualenv from matching, but doesn't necessarily perform other cleanup:
rm "$VIRTUAL_ENV/bin/python"
...or a better-behaved alternative (assuming that you have a python2.7 in your PATH, and that it's what you want to use):
cat >"$VIRTUAL_ENV/bin/python" <<'EOF'
#!/usr/bin/env bash
path_prefix=$VIRTUAL_ENV/bin:
if [[ $PATH = $path_prefix* ]]; then
PATH=${PATH#$path_prefix}
fi
unset PYTHONHOME VIRTUAL_ENV
exec python2.7 "$#"
EOF
The below will assume you're looking for approaches with a bit more finesse.
Command-Specific Shell Wrapper
If you interact with Hortonworks through a frontend called hdp, consider the following a shell function, a wrapper for hdp that deactivates the virtualenv:
hdp() (
if [[ $VIRTUAL_ENV ]]; then
deactivate
fi
exec command hdp "$#"
)
Because this function is using parentheses instead of curly braces, it runs in a subshell -- a separate copy of the shell environment -- so when it runs deactivate, this doesn't impact your parent shell. This also means that the exec command causes the subshell to replace itself with the hdp command, rather than causing your parent shell to terminate.
Generic Shell Wrapper
If you want to be able to run other scripts with your virtualenv temporarily deactivated, consider instead:
# wv == "without virtualenv"
wv() (
if [[ $VIRTUAL_ENV ]]; then
deactivate
fi
exec "$#"
)
...such that wv foo will run foo with the virtualenv deactivated.
How do you define a custom prompt to use when activating a Python virtual environment?
I have a bash script for activating a virtualenv I use when calling specific Fabric commands. I want the shell prompt to say something like "(fab)" so I can easily distinguish it from other shells I have open. Following this example, I've tried:
#!/bin/bash
script_dir=`dirname $0`
cd $script_dir
/bin/bash -c ". .env/bin/activate; PS1='(fab) '; exec /bin/bash -i"
but there's no change to the prompt. What am I doing wrong?
The prompt is set in the virtualenv's activate script (located in the bin folder under the virtualenv). If you only want to change the prompt some times, you could set an environment variable before calling activate (make sure to clear it in the corresponding deactivate file). If you simply want the prompt to be different all the time, you can do that right in activate at the line that looks like
set "PROMPT=(virtualenvname) %PROMPT%"
If you're using virtualenvwrapper, you could do all of this in the postactivate and postdeactivate scripts as well.
I couldn't find any way to do this via a script executed as a child process. Calling a separate bash process seems to forget any previously set PS1. However, it turned out to be trivial if I just sourced the script:
#!/bin/bash
script_dir=`dirname $0`
cd $script_dir
. .env/bin/activate
PS1="(fab) "
It appears the
exec /bin/bash -i
is resetting the PS1 variable. When I run
export PS1="foo "; bash
it resets it too. Curiously, when I look into the bash sources (shell.c and variables.c) it appears to use
set_if_not ("PS1", primary_prompt);
to init it. But I'm not exactly sure what happens between this and main(). Giving up.
I tried on cygwin and on linux (RedHat CentOS) as well. I found solution for both.
CYGWIN
After some investigation I found that the problem is that PS1 is set by /etc/bash.bashrc which overrides the PS1 env.var. So You need to disable to run this file using:
/bin/bash -c ". .env/bin/activate; PS1='(fab) ' exec /bin/bash -i --norc"
or
/bin/bash -c ". .env/bin/activate; export PS1='(fab) '; exec /bin/bash -i --norc"
LINUX
It works much simpler:
/bin/bash -c ". .env/bin/activate; PS1='(fab) ' exec /bin/bash -i"
or
/bin/bash -c ". .env/bin/activate; export PS1='(fab) '; exec /bin/bash -i"
If the script You are calling does not export the variables (and I suppose it does not) and the set variables does not appears in the environment then You could try something like this:
/bin/bash -c "PS1='(fab) ' exec /bin/bash --rcfile .env/bin/activate; "
I hope I could help!
I have some python-2.x scripts which I copy between different systems, Debian and Arch linux.
Debian install python as '/usr/bin/python' while Arch installs it as '/usr/bin/python2'.
A problem is that on Arch linux '/usr/bin/python' also exists which refers to python-3.x.
So every time I copy a file I have to correct the shebang line, which is a bit annoying.
On Arch I use
#!/usr/bin/env python2
While on debian I have
#!/usr/bin/env python
Since 'python2' does not exist on Debian, is there a way to pass a preferred application? Maybe with some shell expansion? I don't mind if it depends on '/bin/sh' existing for example.
The following would be nice but don't work.
#!/usr/bin/env python2 python
#!/usr/bin/env python{2,}
#!/bin/sh python{2,}
#!/bin/sh -c python{2,}
The frustrating thing is that 'sh -c python{2,}' works on the command line: i.e. it calls python2 where available and otherwise python.
I would prefer not to make a make a link 'python2->python' on Debian because then if I give the script to someone else it will not run. Neither would I like to make 'python' point to python2 on Arch, since it breaks with updates.
Is there a clean way to do this without writing a wrapper?
I realize similar question have been asked before, but I didn't see any answers meeting my boundary conditions :)
Conditional shebang line for different versions of Python
--- UPDATE
I hacked together an ugly shell solution, which does the job for now.
#!/bin/bash
pfound=false; v0=2; v1=6
for p in /{usr/,}bin/python*; do
v=($(python -V 2>&1 | cut -c 7- | sed 's/\./ /g'))
if [[ ${v[0]} -eq $v0 && ${v[1]} -eq $v1 ]]; then pfound=true; break; fi
done
if ! $pfound; then echo "no suitable python version (2.6.x) found."; exit 1; fi
$p - $* <<EOF
PYTHON SCRIPT GOES HERE
EOF
explanation:
get version number (v is a bash array) and check
v=($(python -V 2>&1 | cut -c 7- | sed 's/\./ /g'))
if [[ ${v[0]} -eq $v0 && ${v[1]} -eq $v1 ]]; then pfound=true; break; fi
launch found program $p with input from stdin (-) and pass arguments ($*)
$p - $* <<EOF
...
EOF
#!/bin/sh
''''which python2 >/dev/null 2>&1 && exec python2 "$0" "$#" # '''
''''which python >/dev/null 2>&1 && exec python "$0" "$#" # '''
''''exec echo "Error: I can't find python anywhere" # '''
import sys
print sys.argv
This is first run as a shell script. You can put almost any shell code in between '''' and # '''. Such code will be executed by the shell. Then, when python runs on the file, python will ignore the lines as they look like triple-quoted strings to python.
The shell script tests if the binary exists in the path with which python2 >/dev/null and then executes it if so (with all arguments in the right place). For more on this, see Why does this snippet with a shebang #!/bin/sh and exec python inside 4 single quotes work?
Note: The line starts with four ' and their must be no space between the fourth ' and the start of the shell command (which...)
Something like this:
#!/usr/bin/env python
import sys
import os
if sys.version_info >= (3, 0):
os.execvp("python2.7", ["python2.7", __file__])
os.execvp("python2.6", ["python2.6", __file__])
os.execvp("python2", ["python2", __file__])
print ("No sutable version of Python found")
exit(2)
Update Below is a more robust version of the same.
#!/bin/bash
ok=bad
for pyth in python python2.7 python2.6 python2; do
pypath=$(type -P $pyth)
if [[ -x $pypath ]] ; then
ok=$(
$pyth <<##
import sys
if sys.version_info < (3, 0):
print ("ok")
else:
print("bad")
##
)
if [[ $ok == ok ]] ; then
break
fi
fi
done
if [[ $ok != ok ]]; then
echo "Could not find suitable python version"
exit 2
fi
$pyth <<##
<<< your python script goes here >>>
##
I'll leave this here for future reference.
All of my own scripts are usually written for Python 3, so I'm using a modified version of Aaron McDaid's answer to check for Python 3 instead of 2:
#!/usr/bin/env sh
''''which python3 >/dev/null 2>&1 && exec python3 "$0" "$#" # '''
''''test $(python --version 2>&1 | cut -c 8) -eq 3 && exec python "$0" "$#" # '''
''''exec echo "Python 3 not found." # '''
import sys
print sys.argv
Here is a more concise way for the highest voted answer:
#!/bin/sh
''''exec $(which python3 || which python2 || echo python) $0 $# #'''
import sys
print(sys.argv)
print(sys.version_info)
You'll get this if none of them found:
'./test.py: 2: exec: python: not found'
Also, get rid of warnings from linter:
'module level import not at top of file - flake8(E402)'
In my case I don't need the python path or other information (like version).
I have an alias setup to point "python" to "python3". But, my scripts are not aware of the environment alias. Therefore I needed a solution for the scripts to determine the program name, programmatically.
If you have more than one version installed, the sort and tail are going to give you the latest version:
#!/bin/sh
command=$((which python3 || which python2 || which python) | sort | tail -n1 | awk -F "/" '{ print $NF }')
echo $command
Would give a result like: python3
This solution is original but similar to #Pamela