18136


よろしくお願いします。

通常、Pythonでディレクトリの移動は

[code]

    os.chdir('directory')
    
[/code]

でできますが、pythonスクリプト内でディレクトリを移動しても、そのスクリプトを終了したらディレクトリはスクリプトを起動した時のままになります。

[code]

    # pwd -> path/to/file
    os.chdir('foobar')
    # pwd -> path/to/file/foobar
    # この状態でスクリプトを終了すると、
    # pwd -> path/to/file のまま
    
[/code]

こういうことになります。

これを、スクリプトを終了しても **ディレクトリを移動した状態を保つ** ことはできるのでしょうか？

スクリプトを終了する時にあたらしくシェルを起動すれば、ディレクトリの移動は保たれますが、

[code]

    os.chdir('foobar') # pwd -> path/to/file/foobar
    os.system(os.environ["SHELL"]) 
    # path/to/file/foobar の状態でシェルが起動するので、結果的には意図した事ができる
    
[/code]

しかし設計的にこのシステムはよくないと思いますので（シェル変数が設定されていない場合もありますし）、何か解決術がありましたら、お教え頂けませんでしょうか。

よろしくお願いいたします。


CC BY-SA 3.0
2015-10-27T10:53:52.090
18132
2016-05-29T20:18:13.877
11178
question
python
Pythonでディレクトリを移動した状態で終了したい


子プロセス(この場合は python
プロセス)側から親プロセス(この場合はシェルプロセス)のカレントディレクトリを直接的に変更することはできないことになっています(環境によってはできなくもないのですが)。

そこで、「 **ディレクトリを移動した状態を保つ** 」のではなく、「結果的にディレクトリを移動した状態にする」方法を以下に示します。

**chdir.py**

[code]

    #!/usr/bin/python
    
    import os
    import sys
    
    #    :
    
    os.chdir('foobar')
    print(os.getcwd())
    
    sys.exit(0)
    
[/code]

※ エラーチェックは省いています

**シェル側**

[code]

    $ pwd
    /home/nemo
    $ mkdir foobar
    
    $ cd "`./chdir.py`"
    
    $ pwd
    $ /home/nemo/foobar
    
[/code]

Python スクリプトに移動先ディレクトリのパス名を表示させて、それを親プロセス(シェルプロセス)で cd の引数としています。したがって、Python
スクリプトで標準出力や標準エラー出力に他の文字列を出力してしまうと、ディレクトリの移動に失敗することになります。

**追記**

親プロセスのカレントディレクトリを変更する方法について、Linux 系 OSで gdb がインストールされていれば可能かもしれません。

まず、gdb を使うための準備をします。

[code]

    $ sudo sh -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope'
    
[/code]

これで親プロセスに attach することができる様になります。

**chdir.py**

[code]

    os.chdir('foobar')
    
    cmd = "gdb -p %d -ex 'call chdir(\"%s\")' -ex detach -ex quit >/dev/null 2>&1" % \
         (os.getppid(), os.getcwd())
    os.system(cmd)
    
[/code]

親プロセスに attach して `chdir`(system call)
を実行します。成功すれば親プロセス(ここではシェルプロセス)のカレントディレクトリが `./foobar` ディレクトリに変更されているはずです。

[code]

    $ pwd
    /home/nemo
    $ touch ./foobar/here_is_foobar_dir
    
    $ ./chdir.py
    $ pwd ## shell function
    /home/nemo
    $ /bin/pwd
    /home/nemo/foobar
    $ realpath .
    /home/nemo/foobar
    $ ls
    here_is_foobar_dir
    
[/code]

foobar ディレクトリに移動していますが、pwd(bash の shell function)の結果は元のディレクトリのままです。

以上は[本家でも取り上げられていて](https://stackoverflow.com/a/2375174)、

> Don't do this.  
>  :  
>  It will probably work, though note that Bash's pwd command is cached and
> won't notice.

と書かれています。

**追記その2**

bash 4.3.30 の場合ですが、`cd_builtin`(bash の cd function の実体)を実行すると pwd の問題を回避できます。

[code]

    cmd = ("gdb -p %d -ex " + \
           "'call cd_builtin(make_word_list(make_word(\"--\")," + \
           "make_word_list(make_word(\"%s\"), 0)))' " + \
           "-ex detach -ex quit >/dev/null 2>&1") % (os.getppid(), os.getcwd())
    
    $ ./chdir.py
    $ pwd
    /home/nemo/foobar
    $ ls
    here_is_foobar_dir
    
[/code]

バージョンの異なる bash で動作するかは不明です。


CC BY-SA 3.0
2015-10-27T11:45:53.680
18134
2015-10-27T15:30:25.830
2017-05-23T12:38:55.307
-1
18132
answer


Unix系OSについて書きます。  
Unixでは、親プロセスに限らず別プロセスのワーキングディレクトリを変更する一般的な方法はありません。  
どうしても行ないたければ、外部プロセスにアタッチしてシステムコールを実行することになると思います。  
OSによって方法が違うはずですが gdb などのツールを呼び出せば共通化出来るかも知れません。

シェルでのディレクトリ移動を効率化するようなツールを作りたいのでしたら、シェルの alias を使うとよいです。  
移動したいディレクトリを標準出力に出力する`python-script`というコマンドを作ったとすると  

[code]

    alias mycd='cd "$(python-script)"'
    
[/code]

としておけば、`mycd` の実行でシェルのワーキングディレクトリが変更されます。


CC BY-SA 3.0
2015-10-27T11:53:56.350
18136
2015-10-27T11:53:56.350
3054
18132
answer


私も同様の処理 ( python で curses を使い、グラフィカルに移動先ディレクトリを選択する )
を行いたくて、似たようなスクリプトを作ったことがあるのですが、結局 python だけでは完結せずに bash の関数と連携させて実施しました。

そのため、質問への回答とはなりませんが、ご参考になれば。

[code]

    # .bashrcなど
    ccd(){
        /path/to/ccd.py
        cd "`cat /tmp/_ccd_result`"
    }
    
[/code]

ccd.py でパスを選択し、結果を /tmp/_ccd_result に記録して bash 上で cd します。  
泥臭いのであまり人に見せられませんが、結果として有用なので使えています。


CC BY-SA 3.0
2016-01-08T11:14:55.240
20751
2016-01-08T11:14:55.240
10001
18132
answer


sh限定になりますが、こんな方法もありますよ。

main.py

[code]

    #!/bin/sh
    python - <<pass
    print("Hello!")
    pass
    cd /some/where
    
[/code]

呼出しは以下のようにする必要があります。aliasなどで隠蔽できるなら、それでも良いかと思います。

[code]

    $ . main.py
    
[/code]


CC BY-SA 3.0
2016-05-29T18:45:48.243
25272
2016-05-29T20:18:13.877
2016-05-29T20:18:13.877
3605
10596
18132
answer