サブシェルを使用せずにシェルコマンドの置き換えを実行できますか?

サブシェルを使用せずにシェルコマンドの置き換えを実行できますか?

サブシェルを使用せずにコマンド置換が必要なシナリオがあります。私は次の構造を持っています:

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あり、記憶されたディレクトリを管理する方法です。cshbashzsh

pushd /some/dir

宣伝する現在の作業ディレクトリをスタックに追加し、それから現在の作業ディレクトリを変更してから、/some/dirそのスタックの内容を印刷します(スペースで区切ります)。

popd

スタックの内容を空白で区切って印刷し、スタックの最上位要素に変更してスタックからポップします。

~/x(また、いくつかのディレクトリはまたは記号で表示されます~user/x。)

したがって、スタックに現在のディレクトリがあり、/a現在の/bディレクトリがあり、/here実行中の場合:

 pushd /tmp/whatever
 popd

pushd代わりに印刷し/tmp/whatever /here /a /bpopd印刷します。これは、コマンド置換を使用するかどうかには関係ありません。古いディレクトリへのパスを取得するために使用することはできず、その出力は通常後処理できません(ディレクトリスタックの要素にアクセスするには、一部のシェルまたは配列を参照してください)。/here /a /b/tmp/whateverpopd$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ここでの出力をに渡して変数に保存するには、次のようにしますpushdzsh

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")

関連情報