13
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

pushd / popd に気をつけろ!

Posted at

みんな大好き pushd と popd は、ターミナルで作業するときは大変便利です。

しかし、スクリプトの中では pushd / popd よりも、サブシェル実行 ( ... ) を使った方が良いかもしれません。

pushd / popd の問題点

最大の問題は**sudo -u で他のユーザーとして実行しようとした時に失敗する**ことです。

シェルスクリプトは、アプリのデプロイとか、ログの整理とかに使うことが多いと思います。そんな用途では "ディレクトリに移動 → 作業 → 次のディレクトリに移動" という流れになることが多いでしょう。

# update-apps.sh
: 全体の事前準備

for d in /path/to/apps/*; do
  pushd "$d"

    : ファイルをダウンロードしたり
    : 展開したり、削除したり

  popd # ←ここでエラーになる(後述)
done

: 全体の事後処理(完了をメールやチャットに通知するとか)

ところで、デプロイ作業は rootやアプリ専用のユーザーで行いたいはずです。ユーザーを変更するには sudo を使います。

# ssh と sudo を使えば、1コマンドでデプロイを実行できるぞ!
$ ssh myuser@server sudo -u appuser /path/to/update-apps.sh

しかし、ssh myuser@server を実行するとカレントディレクトリは myuser のホーム(/home/myuser)になりますがappuserはそこにアクセスできません。すると、pushd した後、popd/home/myuserに戻ろうとした時、移動先にアクセス権が無いためエラーになるのです。

代替案: ( ... )でサブシェルで実行する

popd に失敗する問題は、sudo-iオプションを付けたり、スクリプトの始めでcdしたりしても回避できます。

しかし、オススメの解決策は( ... ) を使うことです。

# update-apps.sh
# サブシェルを使ったバージョン
: 全体の事前処理

for d in /path/to/apps/*; do
  (
    cd "$d"
    : ファイルをダウンロードしたり
    : 展開したり、削除したり
  )
  # エラーにならない
done

: 全体の事後処理(完了をメールやチャットに通知するとか

( ... ) の中はサブシェルで実行されるのでcd しても外側のカレントディレクトリは変更されません。つまり「元のディレクトリに戻る」操作自体が発生しないので、アクセス権の問題も起きないのです。

また、( ... )で囲むと一塊りの処理であることが見た目にも分かりやすくなります。また、実際に独立したサブシェルで実行されているので、処理の影響範囲を見分けやすくなります。

pushd / popd を使わなくてはならないケース

( ... ) の中身はサブシェルで実行されます。これはメリットでもあるのですが、逆に言えば変数を更新しても外側には反映されません

# Documents/ にあるgitプロジェクトのファイル数を数える(動作しない)

count=0
for d in ~/Documents/*/; do
  (
    cd "$d"
    n="$(git ls-files | wc -l)" # ファイル数を数える
    count="$(( $count + $n ))" # 外側には反映されない代入
  )
done
echo $count # => 0

この場合こそ pushd / popd を使います。

# Documents/ にあるgitプロジェクトのファイル数を数える(動作する)

count=0
for d in ~/Documents/*/; do
  pushd "$d" > /dev/null
    n="$(git ls-files | wc -l)" # ファイル数を数える
    count="$(( $count + $n ))" # 外側には反映されない代入
  popd > /dev/null
done
echo $count # => 正しい合計値

なお、pushdpopd の間はインデントしておくとディレクトリが切り替わっているのが分かりやすくてよいと思います。

また、pushd / popd は 現在スタックに積まれているディレクトリを標準出力に出力するので > /dev/null をつけます。

13
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?