我正在编写一个 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/directoryA
不cd
in /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 | cmd2
、cmd1
和cmd2
中同时运行,因此它们必须在单独的进程中运行。
在许多 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 | tee
where捆绑标准tee
输出上的正常消息和错误消息的另一个改进cd
。
另一个改进是保留了 的退出状态cd
。我添加了一个|| exit
以确保如果cd
失败,我们不会继续执行脚本的其余部分。
请注意,在导航目录堆栈时,仅在 stdout 上输出诸如,cd
之类的内容,而此处不应出现这种情况,因此您可以只使用.cd -
cd -2
cd +2
cd ... 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 -x
,set +x
您会看到 的跟踪:set +x
xtrace
() {
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"
会更有意义。