18

I want to implement a userland command that will take one of its arguments (path) and change the directory to that dir. After the program completion I would like the shell to be in that directory. So I want to implement cd command, but with external program.

Can it be done in a python script or I have to write bash wrapper?

Example:

tdi@bayes:/home/$>python cd.py tdi
tdi@bayes:/home/tdi$>
0

7 Answers 7

23

Others have pointed out that you can't change the working directory of a parent from a child.

But there is a way you can achieve your goal -- if you cd from a shell function, it can change the working dir. Add this to your ~/.bashrc:

go() {
    cd "$(python /path/to/cd.py "$1")"
}

Your script should print the path to the directory that you want to change to. For example, this could be your cd.py:

#!/usr/bin/python
import sys, os.path
if sys.argv[1] == 'tdi': print(os.path.expanduser('~/long/tedious/path/to/tdi'))
elif sys.argv[1] == 'xyz':  print(os.path.expanduser('~/long/tedious/path/to/xyz'))

Then you can do:

tdi@bayes:/home/$> go tdi
tdi@bayes:/home/tdi$> go tdi
Sign up to request clarification or add additional context in comments.

3 Comments

I wrote a script that prints "cd {directory}", which I invoke with `./change_dir` (ie, run the script but surround it with backticks) That makes the interpreter execute whatever the program prints to stdout.
@Shadow, that practice is buggy -- it has all the caveats discussed in BashFAQ #50: I'm trying to put a command in a variable, but complex cases always fail!. In particular, the result of a command substitution skips all the parsing steps other than string-splitting and glob expansion -- quotes aren't honored so there's no way to cd to a directory name with spaces, f/e.
BTW, the original code here didn't work, because tilde expansion doesn't happen to command substitution results. You can't have your Python code write a literal ~ in its output -- it'll get passed to cd without being replaced with an actual directory name; hence, the edit to use os.path.expanduser.
5

That is not going to be possible.

Your script runs in a sub-shell spawned by the parent shell where the command was issued.

Any cding done in the sub-shell does not affect the parent shell.

Comments

2

As codaddict writes, what happens in your sub-shell does not affect the parent shell. However, if your goal is to present the user with a shell in a different directory, you could always have Python use os.chdir to change the sub-shell's working directory and then launch a new shell from Python. This will not change the working directory of the original shell, but will leave the user with one in a different directory.

4 Comments

This answer definitely deserves more attention.
@gspr Can you expand on this approach with code? Is it possible "exec bash" the shell which called the python script?
@user2514157: I don't understand the last sentence. Could you rephrase what you're trying to do?
I want to run a python script from my bash shell which changes the current working directory of the shell (e.g., changes the terminal prompt to the location of a file specified in the script). It is not possible to change the parent shell from a child, however, running exec bash in the terminal replaces the existing parent shell with a new shell that has the desired settings. I thought you were implying a way to accomplish this from a python script. In other words, can a python script change the settings of the shell that called the script?
1

cd is exclusively(?) implemented as a shell internal command, because any external program cannot change parent shell's CWD.

1 Comment

Some Unix varieties have supported an external cd command - it is fairly useless for exactly this reason, but some edge-case uses are discussed at unix.stackexchange.com/questions/50058/…
0

As explained by mrdiskodave in Equivalent of shell 'cd' command to change the working directory? there is a hack to achieve the desired behavior in pure Python. I made some modifications to the answer from mrdiskodave to make it work in Python 3:

  • The pipes.quote() function has moved to shlex.quote().
  • To mitigate the issue of user input during execution, you can delete any previous user input with the backspace character "\x08".

So my adaption looks like the following:

import fcntl
import shlex
import termios
from pathlib import Path

def change_directory(path: Path):
    quoted_path = shlex.quote(str(path))

    # Remove up to 32 characters entered by the user.
    backspace = "\x08" * 32

    cmd = f"{backspace}cd {quoted_path}\n"
    for c in cmd:
        fcntl.ioctl(1, termios.TIOCSTI, c)

Comments

0

Tried a bunch of the answers, none worked in Ubuntu 22.04
The termios.TIOCSTI does not work anymore or it may require sudo.

Eventually just opened another terminal in the new directory all invoked from my python script:

next_proj = '/home/myuserid/full/path/to/dir'
print(f'directory: {next_proj}')
os.system(f'gnome-terminal --tab '
          f'-- bash -c \'cd {next_proj}; exec $SHELL\'')

Not exactly what the question is looking for but may help find a better answer. All that is needed is a way to close the previous terminal (the one the script is running in.)

Comments

-5

I shall try to show how to set a Bash terminal's working directory to whatever path a Python program wants in a fairly easy way.

Only Bash can set its working directory, so routines are needed for Python and Bash. The Python program has a routine defined as:

fob=open(somefile,"w")
fob.write(dd)
fob.close()

"Somefile" could for convenience be a RAM disk file. Bash "mount" would show tmpfs mounted somewhere like "/run/user/1000", so somefile might be "/run/user/1000/pythonwkdir". "dd" is the full directory path name desired.

The Bash file would look like:

#!/bin/bash
#pysync ---Command ". pysync" will set bash dir to what Python recorded
cd `cat /run/user/1000/pythonwkdr`

2 Comments

I have fixed your formatting, but seriously, you really must not have tried very hard, it was a completely unintelligible mess.
Ajean, Thank you. You are a good sport. I hope the post will be of some help to a few. But no, i did not try very hard. I dislike editors that know what you want better than you do. I wrote a reasonably well-indented post, but the editor kept mangling it because I was not hitting some key or other.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.