0

How can I write a Go program that will behave as 'cd ' does?

I tried running a Command, but it didn't work.

func main() {
    cmd := exec.Command("cd", "/media/")
    err := cmd.Run()
    log.Printf("Command finished with error: %v", err)
}
aclowkay
  • 2,561
  • 1
  • 23
  • 48
  • 2
    Possible duplicate of [Why doesn't "cd" work in a shell script?](https://stackoverflow.com/questions/255414/why-doesnt-cd-work-in-a-shell-script) – Flimzy Dec 31 '18 at 09:06
  • 1
    Possible duplicate of [How to change the shell's current working directory in Go?](https://stackoverflow.com/questions/52435908/how-to-change-the-shells-current-working-directory-in-go) – Cerise Limón Dec 31 '18 at 14:22
  • @ThunderCat No, both have different implication. One is to "touch txt" in a particular working directory, the other is to change the caller's directory.. – aclowkay Jan 02 '19 at 07:30

1 Answers1

5

How can I write a Go program that will behave as 'cd ' does?

This is impossible on POSIX systems (even by using any other programming languages).

Because each process, including the parent shell process, has its own current working directory. Hence cd has to be a shell builtin (invoking the chdir(2) system call done by the shell process itself), it cannot be done by some executable, because the shell forks a new process to run every command (using some executable, i.e. which is not a builtin).

Read some good Unix or Linux programming book, such as the ALP, for an explanation. And a good book on Operating systems, such as Operating systems: three easy pieces would explain you why is it so (and cannot reasonably be otherwise, in POSIX-like or Unix-like systems).

Your code is forking (with cmd.Run) a new process, and that child process only is changing its working directory. See also fork(2) & execve(2) & path_resolution(7).

To invoke the chdir(2) system call in your Go code, use os.Chdir. That would only affect your process (and all future child processes created by fork(2), which inherit the working directory of their parent process) and of course won't change the working directory of your parent process (which often is your shell).

To get the current working directory (with the getcwd(2) system call) of your current process, use os.Getwd in your Go code (and see this).

On Linux, you could use /proc/ (see proc(5) for details) to query the working directory of other processes. For example, run ls -l /proc/$$/cwd in your shell. In Go code you'll use os.Readlink on a path like /proc/1234/cwd etc. Of course you cannot change the working directory of some other process (the only working directory that you are allowed to change is the one of your own process, using chdir(2))

As commented by David Budsworth, you could adopt a weird convention that: 1. Your prog program writes a valid (and suitably quoted for the shell) directory path on the standard output, and nothing else. 2. You always use that prog program with a command substitution, something like cd $(prog args...) (which could become the body of a shell function or an alias, etc...), then your shell (not your prog) would change directory as output by your program.

Basile Starynkevitch
  • 1
  • 16
  • 251
  • 479
  • Thanks, I'm actually trying to implement bookmarking program that cd into 'bookmark' name which translates to a directory, any idea on how to workaround this ? – aclowkay Dec 31 '18 at 07:59
  • It is impossible, unless you invoke *directly* `os.Chdir` from your program (and that *won't* change the working directory of the parent shell, only of the process running your program and all its future child processes). Again, you need to read [ALP](http://www.makelinux.net/alp/). There cannot be any "workaround". – Basile Starynkevitch Dec 31 '18 at 08:00
  • Well, the workaround is create an alias that runs the go program which list the path.. `alias gcd=goprogram cd a` – aclowkay Dec 31 '18 at 08:53
  • That is not a workaround. You can't change the fact that every process has its own working directory, and that it is impossible to change the working directory of some other process (such as your parent shell) – Basile Starynkevitch Dec 31 '18 at 08:54
  • 2
    can do via an alias that does `alias gcd=cd [backtick] goprog foo [backtick]` (not sure how to type a literal backtick in comments as that makes a code snippet. but this will run `goprog` with `foo` as an argument, and then `cd` to whatever that outputs – David Budworth Dec 31 '18 at 12:44
  • You'll better use `$(goprog foo)` instead of backticks – Basile Starynkevitch Dec 31 '18 at 16:43