在脚本中更改工作目录的最佳实践是什么?

12
你认为在Bash或Perl脚本中更改目录是可接受的吗?还是应该尽可能避免这样做?
对于这个问题,最佳实践是什么?

我不明白这个问题。你的脚本怎么能让任何人知道它是否切换到了一个新的目录? - S.Lott
目前的答案从“它是有效的语法,不影响父shell,所以没问题”的角度来看待这个问题。我认为这个问题还有另一方面,就像“在脚本中使用cd命令是否是丑陋的编码风格,类似于过长的Python代码行或选择不当的变量名 - 虽然可以做到,但最好避免”的问题。 - dfarrell07
7个回答

26

正如Hugo所说,您无法影响父进程的当前工作目录,因此没有问题。

如果您不控制整个过程,例如在子例程或模块中,则更适用于该问题。在这些情况下,您希望退出子例程时与进入时处于相同的目录,否则会出现导致错误的微妙的跨越操作。

您可以手动完成此操作...

use Cwd;
sub foo {
    my $orig_cwd = cwd;
    chdir "some/dir";

    ...do some work...

    chdir $orig_cwd;
}

但这种方法存在问题。如果子程序提前返回或异常中止(并且异常被捕获),则您的代码仍将在 some/dir 目录下运行。此外,chdir 可能会失败,而您必须记住检查每次使用。呃。

幸运的是,有几个模块可以使这更加容易。File::pushd 是其中之一,但我更喜欢 File::chdir

use File::chdir;
sub foo {
    local $CWD = 'some/dir';

    ...do some work...
}

File::chdir会将目录更改为分配给$CWD。您可以将$CWD本地化,以便在作用域结束时重置,而不管是否成功。它还会自动检查chdir是否成功,否则会引发异常。有时在脚本中使用它非常方便。


我猜File::chdir不是核心模块?如果你想坚持使用核心模块,那么Hugo的方法是下一个最好的选择吗? - SSilk
@SSilk Hugo的技术相当不同,更适用于运行其他命令的构建脚本之类的东西。我的适用于程序和库的内部。两者都试图完全隔离更改目录以防止影响其他任何事情。 - Schwern

16

当前工作目录是相对于执行的shell本地化的,所以除非用户"点"(在当前shell中运行它,而不是通常情况下创建一个新的shell进程运行它),否则您无法影响用户运行脚本。

一个非常好的方法是使用子shell,在别名中我经常这样做。

alias build-product1='(cd $working-copy/delivery; mvn package;)'

使用括号可以确保命令在子shell中执行,因此不会影响我的shell的工作目录。同时,它也不会影响最后一个工作目录,所以cd -;按预期工作。


4

对于Perl,您可以使用来自CPAN的File::pushd模块,使本地更改工作目录变得非常优雅。引用摘要:

  use File::pushd;

  chdir $ENV{HOME};

  # change directory again for a limited scope
  {
      my $dir = pushd( '/tmp' );
      # working directory changed to /tmp
  }
  # working directory has reverted to $ENV{HOME}

  # tempd() is equivalent to pushd( File::Temp::tempdir )
  {
      my $dir = tempd();
  }

  # object stringifies naturally as an absolute path
  {
     my $dir = pushd( '/tmp' );
     my $filename = File::Spec->catfile( $dir, "somefile.txt" );
     # gives /tmp/somefile.txt
  }

4

我并不经常这样做,但有时这样做可以节省很多麻烦。只要确保如果您更改目录,始终要返回到您开始的目录。否则,更改代码路径可能会使应用程序停留在不该去的地方。


在Unix衍生版本之外,它是不可能的。 - Jonathan Leffler
如果你把“应用程序”理解为“相同的可执行文件,但不同的功能”,那么它是可以的 - 你只是不能更改父进程的当前工作目录。 - Tanktalus
什么样的对于“应用程序”一词的解释会让你产生这样的想法呢? - ephemient
@Tanktalus有我想表达的解释,即使我显然解释得不好。在一个应用程序中,如果您多次更改目录并更改代码路径,则应用程序的不同部分可能会期望不同的起始目录点。 - Ovid
一个使用案例是当你的代码在处理多个请求的Web应用程序中时。如果你改变了目录但没有改回来,那么下一个由该子进程处理的请求将会在一个意想不到的位置。在我的情况下,插件代码无法找到sessions目录,因为它使用的是相对路径。 - msouth

3
我会遵循Schwern和Hugo上面的评论。请注意Schwern在意外退出时返回到原始目录的注意事项。他提供了适当的Perl代码来处理这个问题。我将指出shell(Bash,Korn,Bourne)trap命令。

trap“cd $saved_dir”0

将在子shell退出时返回到saved_dir(如果您正在使用该文件)。

mike


1
请注意,Unix 和 Windows 都有一个内置目录堆栈:pushd 和 popd。它们非常容易使用。

0

尝试使用完全限定路径而不对你当前所在的目录进行任何假设,这是否有可能?例如:

use FileHandle;
use FindBin qw($Bin);
# ...
my $file = new FileHandle("< $Bin/somefile");

而不是

use FileHandle;
# ...
my $file = new FileHandle("< somefile");

从长远来看,这可能会更容易,因为您不必担心发生奇怪的事情(脚本在将当前工作目录放回原处之前死亡或被杀死),而且可能更具可移植性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接