如何更改在 zsh shell 中运行的 shell 脚本中的目录

如何更改在 zsh shell 中运行的 shell 脚本中的目录

我正在编写一个 shell 脚本(使用了 shell,其版本和操作系统如下)。该脚本在本地 git 存储库/目录中运行具有多个标签的命令。该脚本从某个目录中的 grep 文件中输出一些字符串,该目录是根据存储库标签名称选择的。

zsh --version
zsh 5.9 (x86_64-apple-darwin22.0)
OS: System Version: macOS 13.6.3
+
using oh-my-zsh, https://ohmyz.sh/

该脚本将目录(cd命令)更改为 git 本地存储库。然后它通过 获取数组中的标签MY_ARRAY=($(git tag))。然后,对于每个存储库标签,它都会git checkout $my_tag --quiet对该标签执行操作。然后cd在存储库的一个目录(directoryA)中。现在它发出 apwd来检查工作目录;输出是/git-repository/directoryA.此时的pwd输出符合预期(/git-repository/directoryA)。现在,在 中 时/git-repository/directoryA,脚本确实获取;LIST_OF_SUBDIRECTORIES=($(ls -d */))的子目录数组。directoryA输出是LIST_OF_SUBDIRECTORIES is cw-events/ cw-metric-alarm/ eventbridge-scheduler/ msk/ secrets-manager/ ses/ sns-topic/ sqs/ ssm-params/ xlate/.现在,对于每个子目录(for使用循环),脚本应该cd指向数组 LIST_OF_SUBDIRECTORIES 中的子目录,如下所示:

if echo "$my_tag" | grep -q -E "$MY_SUBDIRECTORY"; then
          echo "MY_SUBDIRECTORY is $MY_SUBDIRECTORY"
          set -x
          cd $MY_SUBDIRECTORY 2>&1 | tee -a log_file.txt
          set +x
          echo "PWD Working Directory is $(pwd)"

的输出echo "MY_SUBDIRECTORY is $MY_SUBDIRECTORY"MY_SUBDIRECTORY is cw-events/; if 条件为真,所以我希望脚本能够cd输入,cw-events/但上面的内容echo "PWD Working Directory is $(pwd)"正在输出/git-repository/directoryA
该脚本以#!/bin/zsh.尝试中#!/bin/bash我收到一些错误。
我发现了一些其他问题https://blog.kubesimplify.com/how-to-change-directory-in-shell-scripts或者更改当前目录的脚本(cd、pwd)但实际上我不想在父 shell 中更改目录,而是在子 shell 中更改目录。
为什么脚本 while in/git-repository/directoryAcdin /git-repository/directoryA/subdirectory-of-A

输出示例:我可以快速给出的示例是运行脚本的输出:

FIRST-SUB-Directory is /git-repository/directoryA
LIST_OF_SUBDIRECTORIES is cw-events/ secrets-manager/ sqs/ ssm-params/
MY_SUBDIRECTORY is cw-events/
+/path/to/script/script.sh:44> cd cw-events/
+/path/to/script/script.sh:44> tee -a log_file.txt
+/path/to/script/script.sh:45> set +x
PWD Working Directory is /git-repository/directoryA


谢谢。

答案1

这行显然是错误的:

cd $MY_SUBDIRECTORY 2>&1 | tee -a log_file.txt

管道的左侧在子 shell 中运行,这意味着内部更改(变量、当前目录等)不会影响主 shell。看重定向到记录器的 CD 不起作用有关日志记录的更多解释和建议。尽管(除了cd -// cd -2...cd +2在浏览目录堆栈时)cd除非失败,否则不会发出输出,因此记录其输出无论如何都是毫无意义的。

如果您只是将行更改为cd $MY_SUBDIRECTORY(或更好cd -- $MY_SUBDIRECTORY),则更改目录确实有效,至少对于您发布的脚本部分来说是这样。如果稍后在脚本中您仍然位于原始目录中,则您可能正在执行其他操作来创建子 shell(在您尚未发布的代码的某些部分中)。


除此之外,我没有看到任何明显的错误,但有几件事比他们需要的更复杂。我建议以更简单的方式做事,这样就可以减少出错的风险。

  • 你用echo set -x | tee …记录事情。一个就够了。set -x在大多数情况下都很好。管道传输tee很棘手,因为它会影响脚本的行为(创建子 shell、更改文件描述符、隐藏错误)。

  • echo "$my_tag" | grep -q -E "$MY_SUBDIRECTORY"检查 的值是否my_tag包含MY_SUBDIRECTORY作为子字符串的值,假设 的值MY_SUBDIRECTORY不包含扩展正则表达式运算符(例如git 标签中常见的.或)。+有一个更简单的方法来检查:

    if [[ $my_tag = *"$MY_SUBDIRECTORY"* ]]; then …
    
  • LIST_OF_SUBDIRECTORIES=($(ls -d */))LIST_OF_SUBDIRECTORIES=( *(-/) )可以使用 zsh 来编写/ 全局限定符,允许按文件类型和其他特征过滤 glob。这里结合-so 类型检查是在符号链接解析之后完成的*/。这不会在每个元素的末尾添加斜杠。…=( *(-/M) )如果您确实需要斜杠,或者对于仅列出可以使用的目录的特殊情况,则可以使用,…=( */ )但您可能实际上并不需要斜杠。N如果您想获得一个空列表而不是在没有子目录时出现致命错误,请添加glob 限定符:

    LIST_OF_SUBDIRECTORIES=( *(N-/) )
    

echo "$my_tag"严格来说, as输出中的任何行grep一次匹配一行,并且echo还将扩展\n为换行符(值得庆幸的是,git 标签应该保证不包含反斜杠)

答案2

cmd1 | cmd2cmd1cmd2中同时运行,因此它们必须在单独的进程中运行。

在许多 shell 中,即使它们是内置的(例如cd),它们也都在子进程中运行。在 zsh 中,就像在 ksh 中一样,cmd2如果它是内置的,则将在当前 shell 进程中运行,但cmd1始终在子进程中运行。

因此,在您的cd ... | tee ...,cd中运行在子进程中,并且仅更改该短期子进程的工作目录。

不过zsh 具有tee内置的功能,因此您不需要这样做。你可以做:

cd -- $MY_SUBDIRECTORY >&1 >> file.log 2>&2 2>> file.log || exit

当 fd 多次重定向以进行写入时,zsh 会通过管道将其重定向到tee在后台执行类似功能的进程。

在这里,我们将 fd 1 重定向到它已经重定向到 ( >&1) 的内容,这通常是无操作,但这里表示我们希望让 stdout 保持在那里,然后以追加模式将其重定向到file.log。我们对 fd 2 (stderr) 执行相同的操作。这是对你的cd 2>&1 | teewhere捆绑标准tee输出上的正常消息和错误消息的另一个改进cd

另一个改进是保留了 的退出状态cd。我添加了一个|| exit以确保如果cd失败,我们不会继续执行脚本的其余部分。

请注意,在导航目录堆栈时,仅在 stdout 上输出诸如,cd之类的内容,而此处不应出现这种情况,因此您可以只使用.cd -cd -2cd +2cd ... 2>&2 2>> file.log

另请注意,该方法和您的tee方法都不会重定向xtrace输出。它会转到 stderr,但它本身不会输出cd,您需要重定向一个在其中cd运行的命令组来捕获 xtrace 输出。就像是:

set -x # or set -o xtrace
{
  cd -- $MY_SUBDIRECTORY
} 2>&1 2>> file.log 
set +x

您还可以在函数或匿名函数中本地启用该选项,而不是执行set -xset +x您会看到 的跟踪:set +xxtrace

() {
  set -o localoptions -o xtrace
  cd -- $MY_SUBDIRECTORY
} 2>&2 2>> file.log

或者使用辅助函数:

trace() {
  set -o localoptions -o xtrace
  "$@"
} >&1 >> file.log 2>&2 2>> file.log

trace cd -- $MY_SUBDIRECTORY || exit

或者使用functions -t启用对 ksh 中函数的跟踪:

trace() {
  "$@"
} >&1 >> file.log 2>&2 2>> file.log
functions -t trace

trace cd -- $MY_SUBDIRECTORY || exit

Bewareecho默认扩展转义序列,例如\n, \uXXXX, \0123... 并接受选项。为了让它按原样输出后跟换行符的内容,您需要:

echo -E - $something

不过,通常首选使用printf(与 sh 兼容)或(与 ksh 兼容并且更通用):print

printf '%s\n' "$something"
print -r -- "$something"

另请注意,这pwd是一个打印 内容的内置命令$PWD,因此$(pwd)有点毫无意义。

print -r "PWD Working Directory is $PWD"

会更有意义。

相关内容