我有一个场景,要求在不使用子 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
,是管理一堆记住的目录的方法。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
不能用于获取前一个目录的路径,并且通常无法对其输出进行后处理(尽管要访问该目录堆栈的元素,请参阅某些 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
但请注意,pushd
和popd
实际上是交互式 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")