サブシェルを使用せずにコマンド置換が必要なシナリオがあります。私は次の構造を持っています:
pushd $(mktemp -d)
今、一時ディレクトリを一度に終了して削除したいと思います。
rmdir $(popd)
popd
ただし、ポップされたディレクトリは返されず(新しい現在のディレクトリが返されます)、サブシェルで実行されるため機能しません。
それはまるで
dirs -l -1 ; popd &> /dev/null
ポップされたディレクトリを返しますが、次のようには使用できません。
rmdir $(dirs -l -1 ; popd &> /dev/null)
popd
サブシェルにのみ影響するからです。必要なのはこれを行う能力です。
rmdir { dirs -l -1 ; popd &> /dev/null; }
しかし、これは間違った構文です。そのような効果を得ることができますか?
(注:一時ディレクトリを変数に保存できることを知っています。
答え1
あなたの質問のタイトルの選択は少し混乱しています。
pushd
/はコピー機能でpopd
あり、記憶されたディレクトリを管理する方法です。csh
bash
zsh
pushd /some/dir
宣伝する現在の作業ディレクトリをスタックに追加し、それから現在の作業ディレクトリを変更してから、/some/dir
そのスタックの内容を印刷します(スペースで区切ります)。
popd
スタックの内容を空白で区切って印刷し、スタックの最上位要素に変更してスタックからポップします。
~/x
(また、いくつかのディレクトリはまたは記号で表示されます~user/x
。)
したがって、スタックに現在のディレクトリがあり、/a
現在の/b
ディレクトリがあり、/here
実行中の場合:
pushd /tmp/whatever
popd
pushd
代わりに印刷し/tmp/whatever /here /a /b
てpopd
印刷します。これは、コマンド置換を使用するかどうかには関係ありません。古いディレクトリへのパスを取得するために使用することはできず、その出力は通常後処理できません(ディレクトリスタックの要素にアクセスするには、一部のシェルまたは配列を参照してください)。/here /a /b
/tmp/whatever
popd
$dirstack
$DIRSTACK
おそらくあなたは以下が欲しいでしょう:
pushd "$(mktemp -d)" &&
popd &&
rmdir "$OLDPWD"
または
cd "$(mktemp -d)" &&
cd - &&
rmdir "$OLDPWD"
しかし、私は以下を使用します:
tmpdir=$(mktemp -d) || exit
(
cd "$tmpdir" || exit # in a subshell
# do what you have to do in that tmpdir
)
rmdir "$tmpdir"
いずれにせよ、サブシェルでは実行されpushd "$(mktemp -d)"
ません。pushd
その場合、作業ディレクトリを変更することはできません。これはmktemp
サブシェルで実行されます。別のコマンドなので、別のプロセスで実行する必要があります。出力をパイプに書き込み、シェルプロセスはパイプのもう一方の端からそれを読み取ります。
ksh93はコマンドが組み込まれたときに別々のプロセスを避けることができますが、そこにはまだサブシェル(他の作業環境)があり、今回は通常フォークで提供される別の環境に依存せずにエミュレートされます。たとえば、ksh93
分岐a=0; echo "$(a=1; echo test)"; echo "$a"
は含まれませんが、出力はまだecho "$a"
存在します0
。
mktemp
ここでの出力をに渡して変数に保存するには、次のようにしますpushd
。zsh
pushd ${tmpdir::="$(mktemp -d)"}
他のBourneに似たシェルの場合:
unset -v tmpdir
pushd "${tmpdir=$(mktemp -d)}"
あるいは、出力を$(mktemp -d)
変数に明示的に保存せずに複数回使用する場合は、zsh
匿名関数を使用できます。
(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"
答え2
ディレクトリを離れる前にディレクトリの接続を解除できます。
rmdir "$(pwd -P)" && popd
または
rmdir "$(pwd -P)" && cd .. # yes, .. is still usable
しかしpushd
、とはpopd
実際にはスクリプトではなく、インタラクティブシェルのためのツールです(これがあまりにも厄介な理由です。実際のスクリプトコマンドは成功すると沈黙します)。
答え3
同様の問題が発生し、ここにソリューションを公開します。明らかに使用時にサブシェル拡張を避ける方法がないので、$(command)
私が得た唯一の解決策はグローバル変数を使用して関数に渡しますeval
。私の状況は少し難しいです。関数でファイル記述子を開き、fd番号をもう一度基本関数に渡したいと思います。問題は、ファイル記述子がデフォルトシェルではなくサブシェルで開かれることです。
open_fd_and_return() {
local _my_file="${1}" _my_fd_no
exec {_my_fd_no}>"${_my_file}" || die "impossible to open the fd"
echo "${_my_fd_no}"
}
my_file="$(mktemp --dry-run)"
my_fd="$(open_fd_and_return "${my_file}")"
echo "test $$" >&"${my_fd}"
rm "${my_file}"
exec {my_fd}>&-
これにより、「無効なファイル記述子エラー」が返されます。
私の現在の解決策はevalを使用することです(もっと醜い場合でも)。
open_fd_and_return_eval() {
local _my_var="${1}"
local _my_file="${2}"
local _my_fd_no
exec {_my_fd_no}>"${_my_file}" || die "impossible to open the fd"
eval "${_my_var}=\${_my_fd_no}"
}
my_file="$(mktemp --dry-run)"
my_fd=""
open_fd_and_return_eval "my_fd" "${1}"
echo "test $$" >&"${my_fd}"
rm -- "${my_file}"
exec {my_fd}>&-
これはうまくいきます。これが誰かに役立つことを願っています。
答え4
一般的な回答
サブシェルを使用せずにシェルコマンドの置き換えを実行できますか?
問題、コマンド置換はfish
サブシェルで実行されません。
$ set dir (cd /etc; and pwd)
$ echo -- $dir $PWD
/etc /etc
現在の作業ディレクトリを変更しますdir
。
ksh93 および最新バージョンの mksh も、サブシェル${ cmd; }
で実行されないコマンド置換形式をサポートします。
$ dir=${ cd /etc && pwd; }
$ print -r -- "$dir" "$PWD"
/etc /etc
mksh
これには一時ファイルを使用してください。
他のシェルの場合は、いつでも一時ファイルを使用できます。
cmd > "$tempfile"
output=$(cat<"$tempfile")