12

I am on MacOSX using bash as my shell. I have symbolic link, created like this:

ln -s /usr/bin/python python2 

I have a package that uses python2 and I want to create a symbol link in my current working directory to /usr/bin/python which is actually python2. When I do a python2 from the command line I get this error:

python2: realpath couldn't resolve "/usr/bin/python2"

But invoking it like this ./python2 resolves the path correctly. My PATH has . in it. In fact I modified it, for testing, to only have . in it.

How do I resolve this? Thanks!


Context

A number of the suggested solutions below won't work for me. I tried to distill my question as focused and brief as possible so that people wouldn't drown in a sea of text, but clearly I need to provide more background.

I'm trying to do develop on a package that I cloned from git. The original package, git-multimail, is/was developed on some variant of Linux (I'm guessing Ubuntu). I've been trying to modify it to be able to use it and its test suite on MacOSX with as little modification as possible. Here's why some of the proposed solutions aren't ideal:

  1. As root, create a python2 symlink in /usr/bin/. I'm looking for a solution that wouldn't require this. This was an obvious option at the beginning, but I'd like a solution that modifies the host system as little as possible. This is why I wanted to create a temporary symlink in the current working directory, add the CWD (i.e. .) to my path, and then destroy this when finished (i.e. the symlink).

  2. Create a wrapper script to call the python script with the existing python. The problem with this is that much of the test suite uses the actual script_files as executables, depending on shebang to find the correct execution environment. This would mean editing the test suite considerably. In this context, (see below for a snippet of the test-framework), I would have to add a wrapper for every .py file; further the user/developer would have to be aware of different rules for using the package depending on what system they are on (i.e. on MacOSX make sure you don't use the python files without invoking them via the wrapper or explicitly calling /usr/bin/python file.py).

    #! /bin/sh
    
    D=$(cd $(dirname "$0") && pwd)
    MULTIMAIL="$D/../git-multimail/git_multimail.py"
    POST_RECEIVE="$D/../git-multimail/post-receive"
    
    TESTREPO=$("$D/create-test-repo")
    
    HOME="$D"
    XDG_CONFIG_HOME="$D"
    GIT_CONFIG_NOSYSTEM=1
    export HOME XDG_CONFIG_HOME GIT_CONFIG_NOSYSTEM
    
    cd $TESTREPO
    
    test_email() {
        REFNAME="$1"
        OLDREV="$2"
        NEWREV="$3"
        echo "$OLDREV" "$NEWREV" "$REFNAME" | USER=pushuser "$MULTIMAIL"
    
    } 
    
  3. Changing all python2 references to python. The README suggests this, but this effectively makes version control useless since the system sees the change as new version, when in fact it's not (semantically).

I've been using (3) but I'm trying to find a better solution. I'm willing to accept that this is just the way things are (i.e. there's not a suitable way to point 'python2' to /usr/bin/python that is portable and unobtrusive without a lot of changes to the test suite and actual framework).

4
  • 1
    Hey! Just ln -s /usr/bin/python /usr/bin/python2
    – enedil
    Commented Apr 25, 2014 at 22:37
  • Indeed I develop git-multimail on Linux. But I'd welcome patches and testers to make it work on OS X. BTW, your "python2" issue is solved now, as git-multimail accepts both python2 and python3 :-). Commented May 14, 2016 at 21:01
  • The expanded question is still missing a crucial detail: What does the shebang line in the executable scripts look like? For git-multimail.py it is #!/usr/bin/env python2, so there's a (relatively) straightforward way to do this.
    – alexis
    Commented Dec 13, 2016 at 22:31
  • 1
    @enedil's comment didn't work, however ln -s /usr/bin/python2.7 /usr/local/bin/python2 did
    – Phylliida
    Commented Feb 14, 2018 at 20:54

6 Answers 6

17

I believe you're running afoul of Apple's system for managing and switching among multiple versions of the same program. You can accomplish what you want, less elegantly but without problems, with the following script named python2:

#!/bin/bash
exec /usr/bin/python "$@"

Make it executable (chmod +x python2), and you're in business.

Explanation of the problem:

When you run /usr/bin/python, it finds and executes python2.7 in the same directory. Your symbolic link fails because the system follows the symlink to /usr/bin, then looks for and fails to find python2 there. You can get one step further by using a "hard link" instead of a symbolic link:

rm python2
ln /usr/bin/python python2

Now there's no symbolic link to follow, just two filenames for the same file (inode). But now I fail with the following message:

python2: posix_spawn: /Users/alexis/.../python22.7: No such file or directory

Notice the python22.7: The framework is adding 2.7 to the name you've created! Instead of trying to unravel this and set up a forest of links that matches its expectations, I recommend you stay out of the way of the version management framework, and use the solution suggested above.

PS. There might be a better solution: If you would explain what you needed to do to begin with (why do you need to provide python2 as an alias for python), someone can probably help you do it in a different way. This is known as an "XY problem" in stackexchange lingo...

4
  • When you run /usr/bin/python, it finds and executes python2.7 in the same directory. This is very confusing statement. If you describe the case where /usr/bin/python is a symlink then please be more specific. Commented Jul 10, 2014 at 7:41
  • that's just amazingly bad..
    – nicolas
    Commented Oct 30, 2015 at 15:30
  • What is amazingly bad?
    – alexis
    Commented Oct 30, 2015 at 18:07
  • 1
    Yeah what's not explicitly mentioned here is that I think /usr/bin/python is actually not python, it just looks for the MacOS installed python (python2.7 which is symlinked to the frameworks folder). I suspect @nicolas is saying that the fake python version is amazing bad - it is quite the hack and I wish Apple hadn't done so. ...there are many things I wish Apple didn't do which just bork their otherwise fine Unix system.
    – smaudet
    Commented Feb 26, 2020 at 15:26
5

Try:

ln -s /System/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7 /usr/local/bin/python2
2
  • 1
    Don't know why you are getting downvotes, this is the only solution that worked for me
    – xApple
    Commented Apr 12, 2019 at 19:50
  • FWIW, ln -s /usr/bin/python2.7 /usr/local/bin/python2 also works (since /usr/bin/python2.7 is already a symlink to the path in your answer above). Commented Mar 18, 2020 at 23:29
2

If you need to resolve (or investigate) a symlink you can use the platform independent bash library 'realpath-lib'. By default it emulates readlink and will work on Mac or Unix. It can be found on Github or Bitbucket and it's free.

But it sounds like you want to just do python2 (rather than ./python2) from your local (working) directory. It might be possible to do this with an alias in your .bashrc or otherwise you will need to add the working directory (that contains your symlink) to your PATH environment variable. This can also be done for the current session only or within the .bashrc file for future sessions. This could be a solution for just a specific user.

Another option that would work for all users would be to create the python2 symlink to /usr/bin/python in another directory on the path, say in /usr/local/bin. Perhaps something like:

sudo ln -s /usr/bin/python /usr/local/bin/python2

Then any user or script should find the python or python2 commands. Of course this option requires admin (root) privileges to install.

6
  • I eventually gave up. There are too many places which assume 'python2' in the code and not every localized solution covers all the possibilities. This is the most appropriate sledgehammer for this nail. Commented May 5, 2014 at 6:38
  • @Avery, did you try my solution? Was there a problem? It wouldn't have required you to add python2 to /usr/bin (though I view adding it as an improvement, to be honest).
    – alexis
    Commented May 28, 2014 at 8:12
  • It might be possible to do this with an alias (...) It's not possible as alias influences only command line and not scripts. See How to change the default version of Python in Debian 7.5? Commented Jul 10, 2014 at 7:44
  • This is not the correct answer, at least it does not solve the issue that prompted the poster to ask the question.
    – smaudet
    Commented Feb 26, 2020 at 15:31
  • Your suggestion doesn't work for me (using Python 2.7.16). After creating the symlink, I still get the same realpath error as the OP: $ type python2 python2 is /usr/local/bin/python2 $ python2 python2: realpath couldn't resolve "/usr/bin/python2" Commented Mar 18, 2020 at 23:15
1

Re @alexis answer, the python version in /usr/bin is not a real python.

This should be evident in three ways:

  1. The error we are getting is not one which python typically produces. The behavior of python is not to look for another python, its to execute python scripts.

  2. If you compute the shasum of /usr/bin/python and the actually python2.7 which is run:

shasum /usr/bin/python
3782d9ab14b35037c9c7fb665439a5fa695c54a6  /usr/bin/python
shasum /usr/bin/python2.7
476fa96c80ac26a85b2d3b01ddfd19e513660c2c  /usr/bin/python2.7

They are completely different. Additionally, the file sizes differ by ~20k bytes:

ll /usr/bin/python
-rwxr-xr-x  1 root  wheel  66880 May 17  2019 /usr/bin/python

vs.

ll /System/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7
-rwxr-xr-x  1 root  wheel  43104 May 17  2019 /System/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7
  1. Finally, you can decompile the python2.7 and python and investigate the symbols. That's a bit out of scope of just 'how to symlink python2.7 to python2 on osx' though.

So (as the error indicates) /usr/bin/python turns around and looks for /usr/bin/python2.7:

ll /usr/bin/python2.7
lrwxr-xr-x  1 root  wheel  75 Jul  1  2019 /usr/bin/python2.7 -> ../../System/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7

Which is why the following works (courtesy @liangmin-li 's answer):

    ln -s /System/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7 /usr/local/bin/python2
4
  • Welcome to the site. You may want to add some explanation on how to see from your sample ls output that /usr/bin/python is "not a real python".
    – AdminBee
    Commented Feb 26, 2020 at 15:46
  • Its a bit plainly evident but I can update how we can determine that.
    – smaudet
    Commented Feb 27, 2020 at 13:31
  • It may be for someone who knows the size of a typical python interpreter binary, but remember the answers here are supposed to be usable for a more general U&L audience ...
    – AdminBee
    Commented Feb 27, 2020 at 13:39
  • 1
    I've updated my sample. Proving binary identity is a difficult problem though, which is why I don't fully address that here (sizes can be legitimately different between versions, behaviors can change - the evidence as I posted in my updated answer is in how this system behaves versus how a vanilla python installation is supposed to behave. I don't think an answer about reverse engineering the python binary would be appropriate or really helpful here). But I understood your point.
    – smaudet
    Commented Feb 27, 2020 at 13:44
0

You can use the Unix command readlink to find out a links physical path.

Examples

Say I have the following link:

$ ls -l /usr/bin/etags
lrwxrwxrwx. 1 root root 29 Dec 10 22:56 /usr/bin/etags -> /etc/alternatives/emacs.etags

$ ls -l /etc/alternatives/emacs.etags
lrwxrwxrwx. 1 root root 20 Dec 10 22:56 /etc/alternatives/emacs.etags -> /usr/bin/etags.ctags

$ ls -l /usr/bin/etags.ctags
lrwxrwxrwx. 1 root root 5 Dec 10 22:56 /usr/bin/etags.ctags -> ctags

  1. To find the value a symbolic link points to

    $ readlink /usr/bin/etags
    /etc/alternatives/emacs.etags
    

    NOTE: The above result may be another link. To resolve this see #2 below.

  2. To find out the absolute path of the value a symbolic link points to

    $ readlink -f /usr/bin/etags
    /usr/bin/ctags
    
1
  • This is all technically correct, but also entirely irrelevant to the OP's issue. Commented Mar 18, 2020 at 23:33
0

I don't understand - how is it you believe a wrapper link is ok, but not a wrapper script? Either one is merely level of indirection. And wouldn't you still have to instruct your users only to call it from a certain directory?

In any case, you can get the current working directory in $PATH of course:

 echo "echo \"Hi! I'm python\"" >|./python 
 chmod +x ./python 
 PATH="${PWD}:${PATH}" 
 python

 #OUTPUT#
 Hi! I'm python

 rm python 
 python -V 
 ln -s /usr/bin/python2 ./python 
 python -V

 #OUTPUT#
 Python 3.4.0
 Python 2.7.6

 PATH="${PATH#"${PWD}:"}" 
 python -V

 #OUTPUT#
 Python 3.4.0

Please, get . out of your $PATH. That's a horrible idea.

2
  • Why is . in my $PATH a bad idea? If I put it at the end, then the last place that will be looked is my current working directory (i.e. ` PATH="${PATH}:${PWD}". A wrapper script means that for every python file I'll have to create an additional file. A wrapper link to the python executable just creates one thing for me to do. Commented Apr 28, 2014 at 5:00
  • @AveryChan I don't think so. A wrapper script can source a shell function or alias that with the same name as the executable. It can also --bind mount the executable in the current directory or (linux only, I guess) even chroot as necessary. But . in $PATH works for any directory and is not specific. This is dangerous for your users. In any case, I very clearly demonstrated how to do it above. Does it not meet your requirements?
    – mikeserv
    Commented Apr 28, 2014 at 5:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .