Mas*_*son 27 bash shell-script shell-builtin
是否可以制作一个类似的功能
function doStuffAt {
cd $1
# do stuff
}
Run Code Online (Sandbox Code Playgroud)
但是让它如此调用该函数实际上并没有改变我的密码,它只是在函数的持续时间内改变它?我知道我可以保存密码并在最后设置它,但我希望有一种方法可以让它在本地发生而不必担心。
ter*_*don 47
是的。只需让函数在( )子 shell 中运行它的命令而不是{ }组命令:
doStuffAt() (
cd -- "$1" || exit # the subshell if cd failed.
# do stuff
)
Run Code Online (Sandbox Code Playgroud)
括号 ( ( )) 打开一个新的子 shell,它将继承其父级的环境。子shell将在运行它的命令完成后立即退出,将您返回到父shell并且cd只会影响子shell,因此您的PWD将保持不变。
请注意,子shell 还会复制所有shell 变量,因此您无法通过全局变量将信息从子shell 函数传递回主脚本。
有关子外壳的更多信息,请查看man bash:
(列表)
list 在子 shell 环境中执行(请参阅下面的命令执行环境)。影响 shell 环境的变量赋值和内置命令在命令完成后不再有效。返回状态是列表的退出状态。
相比于:
{ 列表; }
list 只是在当前 shell 环境中执行。列表必须以换行符或分号结束。这称为组命令。返回状态是列表的退出状态。请注意,与元字符 ( and ) 不同,{ 和 } 是保留字,必须出现在允许识别保留字的地方。由于它们不会导致断字,因此它们必须通过空格或其他 shell 元字符与列表分开。
Gil*_*il' 18
这取决于。
您可以将该函数放在子shell 中。(另请参阅括号是否真的将命令放入子shell 中?)子shell 中发生的事情会保留在子shell 中。函数对变量、当前目录、重定向、陷阱等所做的更改不会影响调用代码。子shell 从其父shell 继承了所有这些属性,但没有向另一个方向转移。exit在子shell中只退出子shell,而不是调用进程。您可以将一段代码放在一个子shell 中,方法是将其包裹在括号中(括号前后的换行符甚至空格是可选的):
(
set -e # to exit the subshell as soon as an error happens
cd -- "$1"
do stuff # in $1
)
do more stuff # in whatever directory was current before the '('
Run Code Online (Sandbox Code Playgroud)
如果要在子shell 中运行整个函数,可以使用括号代替大括号来包装函数代码。
doStuffAt () (
set -e
cd -- "$1"
# do stuff
)
Run Code Online (Sandbox Code Playgroud)
使用 Korn 风格的函数定义语法,您需要:
function doStuffAt { (
set -e
cd -- "$1"
# do stuff
) }
Run Code Online (Sandbox Code Playgroud)
子shell的缺点是没有什么可以逃脱它。如果您需要更改当前目录但随后更新某些变量,则不能使用子shell 执行此操作。从子shell中检索信息只有两种简单的方法。像任何其他命令一样,子shell 具有退出状态,但这是一个介于 0 和 255 之间的整数,因此它不会传达太多信息。您可以使用命令替换来产生一些输出:命令替换是一个子shell,其标准输出(减去尾随换行符)被收集到一个字符串中。这使您可以输出一个字符串。
data=$(
set -e
cd -- "$1"
do stuff # in $1
)
# Now you're still in the original directory, and you have some data in $data
Run Code Online (Sandbox Code Playgroud)
您可以将当前目录保存到一个变量中,并在以后恢复它。
set -e
old_cwd="$PWD"
cd -- "$1"
…
cd "$old_cwd"
Run Code Online (Sandbox Code Playgroud)
然而,这不是很可靠。如果代码在两个cd命令之间存在,它将位于错误的目录中。如果同时移动旧目录,第二个cd将不会返回到正确的位置。可能位于您无权更改的目录中(因为脚本的权限低于其调用者的权限),在这种情况下,第二个cd将失败。因此,除非您处于不会发生这种情况的受控环境中(例如,cd进出由脚本创建的临时目录),否则您不应该这样做。
如果您需要临时更改目录并以某种方式(例如设置变量)影响 shell 环境,则需要小心地将脚本拆分为影响 shell 环境的部分和更改当前目录的部分。shell 继承了早期 unix 系统的限制,无法返回到以前的目录。现代 unix 系统可以(您可以“保存”当前目录的文件描述符,并fchdir()在异常处理程序中返回它),但此功能没有 shell 接口。
FeR*_*eRD 11
当“临时”进入一个目录做一些工作时——IOW,当你想以某种方式改变目录的范围时——通过使用和利用目录堆栈是有意义的。这是构建脚本之类的常用技术。pushdpopd
假设您正在构建一堆插件。
for plugindir in plugin1 plugin2 plugin2 plugin4; do
pushd -- "$plugindir"
make
popd
done
Run Code Online (Sandbox Code Playgroud)
[这个答案假设bash ; 它不适用于其他外壳]
在大多数实际情况下,使用子shell 既简单又完美,除非您的函数必须修改主脚本中的变量。
对于这种情况,您可以RETURN在从函数返回时使用陷阱更改回旧的当前目录。该RETURN陷阱不管你的函数是如何退出调用,以及其他功能,默认情况下不继承。
doStuffAt(){
cd -- "$1" || return
local opwd=$OLDPWD
trap 'cd "$opwd"' RETURN
#
# do stuff
shift; "$@"
# change some variable
wasAt+=("$PWD")
}
% doStuffAt /
% pwd
/home/user
% doStuffAt /usr
% echo ${wasAt[@]}
/ /usr
Run Code Online (Sandbox Code Playgroud)
但是当您尝试改回旧目录时,旧目录可能已被重命名,并且您将无法通过其路径执行此操作。在Linux 上,您可以/dev/fd在 shell 中模拟打开文件描述符.然后调用fchdir它的更安全的方法:
doStuffAt() {
local fd
command exec {fd}< . || return
trap 'cd -P "/dev/fd/$fd"; exec {fd}<&-' RETURN; exec {fd}<.
cd -- "$1" || return
# do stuff
shift; "$@"
# change some variable
wasAt+=("$PWD")
}
Run Code Online (Sandbox Code Playgroud)
这里{fd}<.与局部变量一起使用,而不是固定的 fd,以便函数可重入。
请注意,cd -P /dev/fd/X即使无法访问其解析路径中的前导目录,它也会成功。简单的例子:
t=$(mktemp -d); mkdir -p $t/b/c; exec 7<$t/b/c; chmod -rwx $t
cd -P /dev/fd/7 # this will succeed
pwd
cd $(pwd) # this will fail
Run Code Online (Sandbox Code Playgroud)
当然,旧的 cwd 可以使其自身无法访问,而不是导致它的目录(通过删除其x模式);但在那种情况下,fchdir(2)也无济于事。
此外,仍然存在删除旧 cwd 的情况。在这种情况下,陷阱可以更改为cd "/dev/fd/$fd"; cd -P . 2>/dev/null(-P主要用于美容目的)。但目前尚不清楚改回空的已删除目录有何用处。