Bash – Saving command output to a variable in bash results in “Unescaped left brace in regex is deprecated”

bashcommand-substitutionescape-characters

Errata:

Similar questions about this have been asked but after searching this for a few days there appears to be no answer to this specific scenario.

Description of the problem:

The second line in in the following bash script triggers the error:

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

Here is the error message:

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

Things I have tried:

  1. I have tried every combination of single quotes, back angled single quotes, double quotes, and spacing I could think of both inside and outside the $() command output capture method.
  2. I have tried using $( exec … ) where … is the command being attempted here.
  3. I have read up on bash, and searched these forums and many others and nothing seems illuminate why this error message is happening or how to work around it.

If the suggestion given in the error message is followed like this:

sessionuser=$( ps -o user= -p 1000 | awk '\{print $1}' )

It results in the following error message combined with the previous one:

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.

The message refers to line 528 in /usr/bin/print. Here is that line:

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

Rational for my bash script:

The string $USER can be rewritten and is therefore not necessarily reliable. The command "whoami" will return different results depending on whether or not privileges have been elevated for the current user.

As such there is a need for reliably attaining the current session users name for portability of scripting, and that is because I am probably not going to keep the same user name forever and would like my scripts to continue working regardless of who I have logged in as.

All of that is because user files are being backed up that have huge directory structures and many files. Every once in a while a file with root ownership and permissions will end up in that backup stack for that user.

There are lots of reasons why this happens and sometimes its just because that user backed up a wallpaper or a theme they like from the system directory structure, or sometimes its because a project was compiled by that user and some of its directories or files needed to be set to root ownership and permissions for it to function in some way, and other times it may be due to some other strange unaccounted for thing.

I understand that rsync might be able to handle this problem, but I'd like to understand how to tackle the "Unescaped left brace" in a Bash script problem first.

I can study rsync on my own, but after trying for a few days this bash script doesn't appear to have a solution that is easy to discover or illuminate through either online searches or reading the manuals.

[UPDATE 01]:

Some information was missing from my original post so I'm adding it here.
Here are the relevant system specs:

  • OS: Xubuntu 16.04 x86_64
  • Bash: GNU bash, version 4.3.46(1)-release (x86_64-pc-linux-gnu)

Source and Rational for the commands I'm using:

3rd reply down in the following thread:

https://stackoverflow.com/questions/19306771/get-current-users-username-in-bash

Print vs. Printf

I posted this question using "print" instead of "printf" because the source I copied it from used the "print" syntax. After using "printf" I get the same error message with an added error message as output:

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"

Where "sessions_username_here" is a replacement of the actual sessions user name for the purpose of keeping the discussion generalized to whatever username could or might be used.

[UPDATE FINAL]

The chosen solution offered by Stéphane Chazelas clarified all the issues my script was having in a single post. I was mistakenly assuming that the 2nd line of the script since the output was complaining about brackets. To be clear it was the 3rd line that was triggering the warning (see Chazelas post for why and how) and that is probably why everyone was suggesting printf instead of print. I just needed to be pointed at the 3rd line of the script in order to make sense of those suggestions.

Things that didn't work as suggested:

 sessionuser=$(logname)

Resulting error message:

 logname: no login name

…so maybe that suggestion isn't quite as reliable as it might seem on the surface.

If user privileges are elevated which is sometimes the case when running scripts then:

 id -un 

would output

 root

and not the current session's user name. This would probably be a simple matter of making sure the script drops out of root privileges before execution which could solve this issue but that is beyond the scope of this thread.

Things that did or could work as suggested:

After I figure out how to verify my script is running in a POSIX environment and somehow de-elevating root privileges, then I could indeed use "id -un" to acquire the current sessions username, but those verifications and de-escilations are beyond the scope of this threads question.

For now "without" POSIX verification, privilege testing, and de-escalation the script does what was originally intended to do without error. Here is what that script looks like now:

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

Note: The above script if run with elevated privileges still outputs "root" instead of the current sessions username even though the privilege escalated command:

 sudo ps -o user= -p $$ | awk '{printf $1}'

will output the current sessions username and not "root" so even though the scope of this thread is answered I am back to square one with this script.

Thanks again to xtrmz, icarus, and especially Stéphane Chazelas who somehow was able catch my misunderstanding of the issue. I'm really impressed with every one here. Thanks for the help! 🙂

Best Answer

It's the third line (print $sessionuser) that causes that error, not the second.

print is a builtin command to output text in ksh and zsh, but not bash. In bash, you need to use printf or echo instead.

Also note that in bash (contrary to zsh, but like ksh), you need to quote your variables.

So zsh's:

print $sessionuser

(though I suspect you meant:

print -r -- $sessionuser

If the intent was to write to stdout the content of that variable followed by a newline) would be in bash:

printf '%s\n' "$sessionuser"

(also works in zsh/ksh).

Some systems also have a print executable command in the file system that is used to send something to a printer, and that's the one you're actually calling here. Proof that it is rarely used is that your implementation (same as mine, as part of Debian's mime-support package) has not been updated after perl's upgrade to work around the fact that perl now warns you about those improper uses of { in regular expressions and nobody noticed.

{ is a regexp operator (for things like x{min,max}). Here in %{(.*?)}, that (.*?) is not a min,max, still perl is lenient about that and treats those { literally instead of failing with a regexp parsing error. It used to be silent about that, but it now reports a warning to tell you you probably have a problem in your (here print's) code: either you intended to use the { operator, but then you have a mistake within. Or you didn't and then you need to escape those {.

BTW, you can simply use:

sessionuser=$(logname)

to get the name of the user that started the login session that script is part of. That uses the getlogin() standard POSIX function. On GNU systems, that queries utmp and generally only works for tty login sessions (as long as something like login or the terminal emulator registers the tty with utmp).

Or:

sessionuser=$(id -un)

To get the name of one user that has the same uid as the effective user id of the process running id (same as the one running that script).

It's equivalent to your ps -p "$$" approach because the shell invocation that would execute id would be the same as the one that expands $$ and apart from zsh (via assignment to the EUID/UID/USERNAME special variables), shells can't change their uids without executing a different command (and of course, of all commands, id would not be setuid).

Both id and logname are standard (POSIX) commands (note that on Solaris, for id like for many other commands you'd need to make sure you place yourself in a POSIX environment to make sure you call the id command in /usr/xpg4/bin and not the ancient one in /bin. The only purpose of using ps in the answer you linked to is to work around that limitation of /bin/id on Solaris).

If you want to know the user that called sudo, it's via the $SUDO_USER environment variable. That's a username derived by sudo from the real user id of the process that executed sudo. sudo later changes that real user id to that of the target user (root by default) so that $SUDO_USER variable is the only way to know which it was.

Note that when you do:

sudo ps -fp "$$"

That $$ is expanded by the shell that invokes sudo to the pid of the process that executed that shell, not the pid of sudo or ps, so it will give not give you root here.

sudo sh -c 'ps -fp "$$"'

Would give you the process that executed that sh (running as root) which is now either still running sh or possibly ps for sh invocations that don't fork an extra process for the last command.

That would be the same for a script that does that same ps -p "$$" and that you run as sudo that-script.

Note that in any case, neither bash nor sudo are POSIX commands. And there are many systems where neither are found.

Related Question