将命令输出保存到 bash 中的变量会导致“不推荐使用正则表达式中未转义的左大括号”

neu*_*lly 4 bash escape-characters command-substitution

勘误表:

已经提出了类似的问题,但在搜索了几天之后,似乎没有针对这种特定情况的答案。

问题描述:

以下 bash 脚本中的第二行触发错误:

#!/bin/bash
sessionuser=$( ps -o user= -p $$ | awk '{print $1}' )
print $sessionuser
Run Code Online (Sandbox Code Playgroud)

这是错误消息:

Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/%{ <-- HERE (.*?)}/ at /usr/bin/print line 528.
Run Code Online (Sandbox Code Playgroud)

我尝试过的事情:

  1. 在 $() 命令输出捕获方法的内部和外部,我已经尝试了单引号、后斜单引号、双引号和间距的所有组合。
  2. 我曾尝试使用 $( exec ... ) 其中 ... 是此处尝试的命令。
  3. 我已经阅读了 bash,并搜索了这些论坛和许多其他论坛,但似乎没有说明为什么会出现此错误消息或如何解决它。

如果错误消息中给出的建议是这样的:

sessionuser=$( ps -o user= -p 1000 | awk '\{print $1}' )
Run Code Online (Sandbox Code Playgroud)

它导致以下错误消息与上一个错误消息相结合:

awk: cmd. line:1: \{print $1}
awk: cmd. line:1: ^ backslash not last character on line
Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/%{ <-- HERE (.*?)}/ at /usr/bin/print line 528.
Run Code Online (Sandbox Code Playgroud)

该消息是指 /usr/bin/print 中的第 528 行。这是那一行:

$comm =~ s!%{(.*?)}!$_="'$ENV{$1}'";s/\`//g;s/\'\'//g;$_!ge;
Run Code Online (Sandbox Code Playgroud)

我的 bash 脚本的合理性:

字符串 $USER 可以重写,因此不一定可靠。命令“whoami”将返回不同的结果,具体取决于当前用户的权限是否已提升。

因此,需要可靠地获取当前会话用户名以实现脚本的可移植性,这是因为我可能不会永远保留相同的用户名,并且希望我的脚本继续工作,无论我登录了谁作为。

所有这一切都是因为正在备份具有庞大目录结构和许多文件的用户文件。每隔一段时间,具有 root 所有权和权限的文件将最终出现在该用户的备份堆栈中。

发生这种情况的原因有很多,有时只是因为该用户从系统目录结构中备份了他们喜欢的壁纸或主题,或者有时是因为该用户编译了项目及其某些目录或文件需要设置为 root 所有权和权限以使其以某种方式运行,有时可能是由于其他一些奇怪的下落不明的事情。

我知道 rsync 可能能够处理这个问题,但我想先了解如何解决 Bash 脚本问题中的“未转义的左大括号”。

我可以自己研究 rsync,但是在尝试了几天之后,这个 bash 脚本似乎没有一个可以通过在线搜索或阅读手册轻松发现或阐明的解决方案。

[更新 01]:

我的原始帖子中缺少一些信息,因此我将其添加到此处。以下是相关的系统规格:

  • 操作系统: Xubuntu 16.04 x86_64
  • Bash: GNU bash,版本 4.3.46(1)-release (x86_64-pc-linux-gnu)

我正在使用的命令的来源和理性:

在以下线程中的第 3 个回复:

/sf/ask/1351474001/

打印与 Printf

我使用“print”而不是“printf”发布了这个问题,因为我复制它的源使用了“print”语法。使用“printf”后,我收到相同的错误消息,并在输出中添加了一条错误消息:

Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/%{ <-- HERE (.*?)}/ at /usr/bin/print line 528.
Error: no such file "sessions_username_here"
Run Code Online (Sandbox Code Playgroud)

其中“sessions_username_here”是对实际会话用户名的替换,目的是将讨论概括为可以或可能使用的任何用户名。

[更新最终]

Stéphane Chazelas 提供的解决方案在一个帖子中澄清了我的脚本存在的所有问题。由于输出抱怨括号,我错误地假设脚本的第二行。需要明确的是,触发警告的是第 3 行(有关原因和方式,请参阅 Chazelas 的帖子),这可能就是每个人都建议使用 printf 而不是打印的原因。我只需要指出脚本的第 3 行即可理解这些建议。

没有按照建议工作的事情:

 sessionuser=$(logname)
Run Code Online (Sandbox Code Playgroud)

结果错误信息:

 logname: no login name
Run Code Online (Sandbox Code Playgroud)

...所以也许这个建议并不像表面上看起来那么可靠。

如果在运行脚本时有时会出现用户权限提升的情况,则:

 id -un 
Run Code Online (Sandbox Code Playgroud)

会输出

 root
Run Code Online (Sandbox Code Playgroud)

而不是当前会话的用户名。这可能是确保脚本在执行前退出 root 权限的简单问题,这可以解决这个问题,但这超出了本线程的范围。

已经或可以按照建议工作的事情:

在我弄清楚如何验证我的脚本在 POSIX 环境中运行并以某种方式降低 root 权限之后,我确实可以使用“id -un”来获取当前会话的用户名,但是这些验证和降级超出了这个线程问题的范围。

现在,“没有” POSIX 验证、权限测试和降级,该脚本执行最初打算执行的操作而不会出错。这是该脚本现在的样子:

 #!/bin/bash
 sessionuser=$( ps -o user= -p $$ | awk '{printf $1}' )
 printf '%s\n' "$sessionuser"
Run Code Online (Sandbox Code Playgroud)

注意:即使特权升级命令,上述脚本如果以提升的权限运行仍然输出“root”而不是当前会话用户名:

 sudo ps -o user= -p $$ | awk '{printf $1}'
Run Code Online (Sandbox Code Playgroud)

将输出当前会话的用户名而不是“root”,所以即使回答了这个线程的范围,我也回到了这个脚本的第一个。

再次感谢 xtrmz、icarus,尤其是 Stéphane Chazelas,他以某种方式发现了我对这个问题的误解。我对这里的每一位都印象深刻。谢谢您的帮助!:)

Sté*_*las 5

print $sessionuser导致该错误的是第三行 ( ),而不是第二行。

print是一个内置命令,用于在ksh和 中输出文本zsh,但不是bash。在 中bash,您需要使用printfecho代替。

另请注意,在bash(与 相反zsh,但类似于ksh)中,您需要引用您的变量。

所以zsh是:

print $sessionuser
Run Code Online (Sandbox Code Playgroud)

(虽然我怀疑你的意思是:

print -r -- $sessionuser
Run Code Online (Sandbox Code Playgroud)

如果意图是写入标准输出,则该变量的内容后跟换行符)将在bash

printf '%s\n' "$sessionuser"
Run Code Online (Sandbox Code Playgroud)

(也适用于zsh/ ksh)。

某些系统print在文件系统中还有一个可执行命令,用于向打印机发送信息,这就是您在此处实际调用的命令。很少使用它的证据是您的实现(与我的相同,作为 Debian 的 mime-support 包的一部分)在perl's 升级后没有更新以解决perl现在警告您{在正则表达式和没有人注意到。

{是一个正则表达式运算符(用于诸如x{min,max})。在这里%{(.*?)},这(.*?)不是一个min,max,仍然perl对它很宽容,并{从字面上处理它们,而不是因正则表达式解析错误而失败。它曾经对此保持沉默,但现在它会报告一个警告,告诉您您的(此处print的)代码中可能存在问题:要么您打算使用该{运算符,但随后又出现了错误。或者你没有,然后你需要逃避那些{

顺便说一句,您可以简单地使用:

sessionuser=$(logname)
Run Code Online (Sandbox Code Playgroud)

获取启动脚本所属登录会话的用户的名称。使用getlogin()标准的 POSIX 函数。在 GNU 系统上,该查询utmp通常仅适用于 tty 登录会话(只要类似login或终端模拟器使用 注册 tty utmp)。

或者:

sessionuser=$(id -un)
Run Code Online (Sandbox Code Playgroud)

获取与正在运行的进程的有效用户 ID 具有相同 uid 的一个用户的名称id(与运行该脚本的用户 ID相同)。

这相当于给你ps -p "$$"的做法,因为这会执行shell调用id将是相同的一个膨胀$$和分开zsh(通过分配到EUID/ UID/USERNAME特殊变量),而不执行不同的命令shell不能改变他们的UID(和当然,所有的命令,id不能为setuid)。

这两个idlogname是标准(POSIX)命令(请注意,在Solaris上,对于id像许多其它命令你需要确保你置身在一个POSIX环境,以确保你调用id的命令/usr/xpg4/bin,而不是古一在/binps在您链接的答案中使用的唯一目的是解决/bin/idSolaris上的限制)。

如果您想知道调用 的用户sudo,则是通过$SUDO_USER环境变量。这是sudo从执行进程的真实用户 ID派生的用户名sudosudo稍后将该真实用户 ID 更改为目标用户的 ID(root默认情况下),以便该$SUDO_USER变量是知道它是哪个的唯一方法。

请注意,当您执行以下操作时:

sudo ps -fp "$$"
Run Code Online (Sandbox Code Playgroud)

$$是由调用sudo执行该 shell 的进程的 pid的 shell 扩展的,而不是 pid 的sudoor ps,所以它不会root在这里给你。

sudo sh -c 'ps -fp "$$"'
Run Code Online (Sandbox Code Playgroud)

将为您提供执行该sh(运行为root)的进程,该进程现在仍在运行sh或可能ps用于sh不为最后一个命令分叉额外进程的调用。

对于执行相同操作ps -p "$$"并且您作为sudo that-script.

请注意,在任何情况下,POSIX 命令都不是,bash也不sudo是。并且有很多系统都没有找到。