是否可以在不使用子 shell 的情况下执行 shell 命令替换?

是否可以在不使用子 shell 的情况下执行 shell 命令替换?

我有一个场景,要求在不使用子 shell 的情况下进行命令替换。我有一个这样的构造:

pushd $(mktemp -d)

现在我想一次性退出并删除临时目录:

rmdir $(popd)

但是,这不起作用,因为popd不返回弹出的目录(它返回新的当前目录),而且还因为它是在子 shell 中执行的。

就像是

dirs -l -1 ; popd &> /dev/null

将返回弹出的目录,但不能像这样使用:

rmdir $(dirs -l -1 ; popd &> /dev/null)

因为popd只会影响子shell。所需要的是做到这一点的能力:

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 /b并将popd输出/here /a /b,而不是/tmp/whatever。这与是否使用命令替换无关。popd不能用于获取前一个目录的路径,并且通常无法对其输出进行后处理(尽管要访问该目录堆栈的元素,请参阅某些 shell 的$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在子 shell 中运行。如果确实如此,则无法更改工作目录。那是mktemp在子 shell 中运行的。由于它是一个单独的命令,因此它必须在单独的进程中运行。它将输出写入管道,shell 进程在管道的另一端读取它。

当命令是内置的时,ksh93 可以避免单独的进程,但即使在那里,它仍然是一个子 shell(不同的工作环境),这次是模拟的,而不是依赖于通常由 fork 提供的单独环境。例如,在ksh93,中a=0; echo "$(a=1; echo test)"; echo "$a",不涉及分叉,但仍然echo "$a"输出0

在这里,如果您想将 的输出存储mktemp在变量中,同时将其传递给pushd, with zsh,您可以这样做:

pushd ${tmpdir::="$(mktemp -d)"}

对于其他类似 Bourne 的 shell:

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

但请注意,pushdpopd实际上是交互式 shell 的工具,而不是脚本的工具(这就是它们如此喋喋不休的原因;真正的脚本命令在成功时是沉默的)。

答案3

我遇到了类似的问题,所以我将在这里发布我的解决方案。显然,在使用时没有办法避免跨越子 shell,$(command)所以我得到的唯一解决方案是使用全局变量 -> 将其传递给函数并使用eval。我的情况有点棘手。我想在函数中打开文件描述符并将 fd 编号传回主函数。问题是文件描述符是在子 shell 中打开的,而不是在主 shell 中打开的:


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

回答通用的

是否可以在不使用子 shell 的情况下执行 shell 命令替换?

问题,命令替换fish不在子 shell 中运行:

$ set dir (cd /etc; and pwd)
$ echo -- $dir $PWD
/etc /etc

确实更改当前工作目录并设置dir.

ksh93 和最新版本的 mksh 还支持一种${ cmd; }不在子 ​​shell 中运行的命令替换形式。

$ dir=${ cd /etc && pwd; }
$ print -r -- "$dir" "$PWD"
/etc /etc

mksh为此使用临时文件。

对于其他 shell,您始终可以使用临时文件:

cmd > "$tempfile"
output=$(cat<"$tempfile")

相关内容