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


11

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

pushd $(mktemp -d)

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

rmdir $(popd)

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

就像是

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

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

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

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

rmdir { dirs -l -1 ; popd &> /dev/null; }

但这是无效的语法。有可能达到这种效果吗?

(注意:我知道我可以将临时目录保存在变量中;我试图避免这样做,并在此过程中学习新知识!)


1
我将目录保存到变量中;这样,trap如果进程被信号戳,则处理程序可以清理目录。
2016年

答案是否定的
h0tw1r3

它看起来像on fish,等效于命令替换(),更改了外壳的文件夹。通常这很烦人,但是我敢肯定在这种情况下它很有用。
trysis

Answers:


10

问题标题的选择有些混乱。

pushd/ popdcshbash和复制的功能zsh,是一种管理记住的目录堆栈的方法。

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/whatever。这与是否使用命令替换无关。popd不能用于获取前一个目录的路径,并且一般而言,其输出不能进行后处理(尽管访问该目录堆栈的元素,请参见某些shell 的$dirstackor $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(不同的工作环境),这次可以对其进行仿真,而不是依赖于通常由分支提供的单独环境。例如,在ksh93a=0; echo "$(a=1; echo test)"; echo "$a",不涉及fork,但仍echo "$a"输出0

在这里,如果要将的输出存储mktemp在一个变量中,同时将的输出传递给pushd,则zsh可以执行以下操作:

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

与其他类似伯恩的贝壳:

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

或者,要$(mktemp -d)多次使用输出而不将其显式存储在变量中,可以使用zsh匿名函数:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"

我理解pushdpopd按照您的描述进行工作,并且它们独立于命令替换-不会引起混淆!但是,您通过揭示自己的问题来回答$OLDPWD。我能做popd; rmdir $OLDPWD。那就是我的问题的答案-其他所有内容都证实了我的想法。命令替换似乎是解决问题的一种方法,但这不是因为有子外壳,没有子外壳您就无法进行命令替换,因此,感谢您揭示OLDPWD-这正是我所需要的!
starfry

@starfry,rmdir $(popd)导致popd在子shell中运行,这意味着它不会更改当前目录,但是即使它不在子shell中运行,其输出popd也不会是临时目录,而是一个用空格分隔的列表目录不包括该临时目录。我是在说你很困惑的地方。
斯特凡Chazelas

但我想我曾在我的问题中这样说过:“ popd不会返回弹出的目录(它会返回新的目录,现在返回当前目录),而且还因为它是在子shell中执行的。” 诚然,它返回一个列表,但是列表中的第一个是我所指的那个。
starfry

@starfry,好的,抱歉。假设我是一个对问题标题感到困惑的人,对其余内容的阅读不够仔细。
斯特凡Chazelas

好吧,您的回答仍然很好,并向我指出了正确的方向。我从来不知道那个shell变量OLDPWD。我必须记得有一天从头到尾读完man bash了!
starfry

2

您可以先取消链接目录,然后再离开目录:

rmdir "$(pwd -P)" && popd

要么

rmdir "$(pwd -P)" && cd ..   # yes, .. is still usable

但要注意,pushdpopd真的工具在交互shell,而不是脚本(这就是为什么他们是如此繁琐;真正的脚本命令是沉默的时候他们成功)。


0

在bash中,dirs通过push / popd方法提供记住的目录列表。

另外,dirs -1打印列表中包括的最后一个目录。

因此,要删除先前通过执行创建的目录pushd $(mktmp -d),请使用:

rmdir $(dirs -1)

然后,popd从列表中删除已经存在的目录:

popd > /dev/null

一站式:

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

并添加选项(-l)避免使用~/x~user/x表示法:

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

这与您要求的行非常相似。
除非我不使用&>,否则它将隐藏任何错误报告popd

注:该目录将保持后rmdir,因为它是pwd在这一点上。并且实际上将在popd命令后取消链接(不使用剩余链接)。


对于支持变量“ OLDPWD”的shell,有一个选项可以使用(大多数类似bourne的shell:ksh,bash,zsh have $OLDPWD)。有趣的是,ksh并没有实现默认情况下推送的dirs,pod(lksh,dash和其他也没有popd可用,因此在这里不可用):

popd && rmdir "$OLDPWD"

或者,更惯用(与上述壳相同):

popd && rmdir ~-

这个问题以及我试图解决的问题是,您无法rmdir在执行此操作时所在的当前目录。您需要popd先进行操作,rmdir但是您需要知道要删除的内容并且popd不会告诉您。我和您一样,以为dirs -l -1是答案,但后来发现答案实际上是要用的$OLDPWD
starfry

@starfry我认为最短的答案是使用:popd && rmdir ~-

@starfry您可以实际上删除当前目录,没问题,只要它为空(不强制擦除)即可。您甚至可以打电话rmdir "$PWD"。另外,当前目录应该是由mktmp -d(如果pushd $(mktemp -d)已事先执行)创建的目录,并且pushd会将pwd移动到该目录。因此,是的, popd 之后 pushd之前,创建的目录存储在中$(dirs -1),我看不到任何问题。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.