printf "-1" in bash gives error for unknown reason - bash

In bash 3.2 (default on the recent macOS), running printf "-1" gives me some error like this:
bash: printf: -1: invalid option
Which I have no problem in zsh, indeed, a leading hyphen in any string passed to printf will trigger that error. shellcheck has not warning for this. Also tried echo "-1" and seems fine.
I know the error is avoidable by using printf "%s" "-1", but can someone explain the reason behind it? thanks.

For utilities that conform to the standard syntax (echo being a notable exception), leading operands starting with - indicate options. Operands are treated as options even if the command does not provide options by those names, causing the error message you've seen. An explicit end of the options can be indicated with --: printf -- -1 will cause the string -1 to be interpreted as the format string and printed.

Related

Unbold piped text in Bash [duplicate]

Weird question. When I set a variable in Bash to display as a certain color, I don't know how to reset it. Here is an example:
First define the color code:
YELLOW=$(tput setaf 3)
RESET=$(tput sgr0)
Now set the error message variable and color part of it.
ERROR="File not found: "$YELLOW"Length.db$RESET"
This sets the variable ERROR as the error message to be returned from a function that will eventually be displayed on the terminal. The error will be all white with the exception of the file name. The file name is highlighted yellow for the user.
This works great except when logging with rsyslog. When the error message gets logged, it comes out something like this:
File not found: #033[33mLength.db#033(B#033[m
This obviously makes log files very difficult to read. At first I figured I could process using sed the error message immediately after outputting to the terminal but before logging, but there is nothing to search for and replace. ie, I thought I could use sed to do something similar to this:
ERROR=$(echo "$ERROR" | sed -r 's%\#033\[33m%%')
But those characters are not present when you echo the variable (which makes sense since you dont see it on the terminal). So im stuck. I dont know how to reset the color of the variable after setting it. I also tried to reverse the process somehow using $RESET but maybe my syntax is wrong or something.
You almost had it. Try this instead:
ERROR=$(echo "$ERROR" | sed 's%\o033\[33m%%g')
Note, however, that the use of the \oNNN escape sequence in sed is a GNU extension, and thus not POSIX compliant. If that is an issue, you should be able to do something more like:
ERROR=$(echo "$ERROR" | sed 's%'$(echo -en "\033")'\[33m%%g')
Obviously, this will only work for this one specific color (yellow), and a regex to remove any escape sequence (such as other colors, background colors, cursor positioning, etc) would be somewhat more complicated.
Also note that the -r is not required, since nothing here is using the extended regular expression syntax. I'm guessing you already know that, and that you just included the -r out of habit, but I mention it anyway just for the sake of clarity.
Here is a pure Bash solution:
ERROR="${ERROR//$'\e'\[*([0-9;])m/}"
Make it a function:
# Strips ANSI codes from text
# 1: The text
# >: The ANSI stripped text
function strip_ansi() {
shopt -s extglob # function uses extended globbing
printf %s "${1//$'\e'\[*([0-9;])m/}"
}
See:
Bash Shell Parameter Expansion
Bash Pattern-Matching

bash associative key with a Variable key

Im trying to create an array and then get the value of a key using the following commands:
declare -A email_addresses
mail_address=(["dev"]="dev.com" ["sandbox"]="sandbox.com")
env=$(#command to get env) # result is "sandbox"
echo ${email_address[$env]}
However it keeps throwing this error at me : -bash: "hsandbox": syntax error: operand expected (error token is ""sandbox"")
Im not sure how to get past this. If I do echo $env it returns "sandbox" and not ""sandbox"" so Im not sure what seems to be the issue.
Fix your "command to get env" to not be emitting literal quotes in its output. Barring that:
# strip leading and trailing quotes from env
env=${env%'"'}; env=${env#'"'}
echo "${email_address[$env]}"
Python-Audience-Friendly Explanation
To explain this in a manner that makes sense to folks who know Python (since that's where most of the OP's rep comes from):
echo "$foo" in shell behaves like the Python command print str(foo), not the Python command print repr(foo).
Consider the following REPL session:
>>> mail_address = { "dev": "dev.com", "sandbox": "sandbox.com" }
>>> env = getSomething()
>>> print str(env)
"dev"
>>> print mail_address[env]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: '"dev"'
>>> print repr(env)
'"dev"'
You've got the exact same problem: Your dictionary contains dev as its literal contents, but your key's literal contents are "dev".
Avoiding Confusion In The Future: Unambiguously Printing Shell Variables
If you want to print a variable's contents in shell in a way that's unambiguous (in the same respect in which print repr(env) is unambiguous in Python), echo is the wrong tool for the job. Consider instead one of the following:
$ declare -p env ## caveat: doesn't work for all non-printable characters
declare -- env="\"dev\""
$ printf 'env=%q\n' "$env" ## caveat: doesn't work for non-string datatypes
env=\"dev\"
An Aside: Why You Should Always Quote Arguments To echo (Or Not Use It)
While it looks innocuous, the code
echo $foo
actually has surprisingly complicated behavior. Consider the following:
foo=$'\thello\tworld\t*\n\\text'
That's the bash equivalent to the following Python:
foo='\thello\tworld\t*\n\\text'
Now, let's see what happens if you actually use echo to print it with echo $foo, if you have a default value for IFS and your shell is bash:
The first tab disappears altogether
The other tabs are replaced by spaces
The newline literal is replaced by a space
The * is replaced by a list of files in the current directory.
That is to say, the behavior in bash of echo $foo is equivalent to the following Python:
import itertools, glob
foo='\thello\tworld\t*\n\\text'
print ' '.join(itertools.chain(*[ glob.glob(s) for s in foo.split() ]))
By contrast, consider:
echo "$foo"
In that case, you'll get the expected behavior... in bash.
Why "in bash"? Because the POSIX standard for echo doesn't specify behavior when any backslash literal is included in the text. echo could do literally anything in this circumstance and still be POSIX-compliant, and BSD-style implementations will behave differently than XSI-style ones do.

How to strip ANSI escape sequences from a variable?

Weird question. When I set a variable in Bash to display as a certain color, I don't know how to reset it. Here is an example:
First define the color code:
YELLOW=$(tput setaf 3)
RESET=$(tput sgr0)
Now set the error message variable and color part of it.
ERROR="File not found: "$YELLOW"Length.db$RESET"
This sets the variable ERROR as the error message to be returned from a function that will eventually be displayed on the terminal. The error will be all white with the exception of the file name. The file name is highlighted yellow for the user.
This works great except when logging with rsyslog. When the error message gets logged, it comes out something like this:
File not found: #033[33mLength.db#033(B#033[m
This obviously makes log files very difficult to read. At first I figured I could process using sed the error message immediately after outputting to the terminal but before logging, but there is nothing to search for and replace. ie, I thought I could use sed to do something similar to this:
ERROR=$(echo "$ERROR" | sed -r 's%\#033\[33m%%')
But those characters are not present when you echo the variable (which makes sense since you dont see it on the terminal). So im stuck. I dont know how to reset the color of the variable after setting it. I also tried to reverse the process somehow using $RESET but maybe my syntax is wrong or something.
You almost had it. Try this instead:
ERROR=$(echo "$ERROR" | sed 's%\o033\[33m%%g')
Note, however, that the use of the \oNNN escape sequence in sed is a GNU extension, and thus not POSIX compliant. If that is an issue, you should be able to do something more like:
ERROR=$(echo "$ERROR" | sed 's%'$(echo -en "\033")'\[33m%%g')
Obviously, this will only work for this one specific color (yellow), and a regex to remove any escape sequence (such as other colors, background colors, cursor positioning, etc) would be somewhat more complicated.
Also note that the -r is not required, since nothing here is using the extended regular expression syntax. I'm guessing you already know that, and that you just included the -r out of habit, but I mention it anyway just for the sake of clarity.
Here is a pure Bash solution:
ERROR="${ERROR//$'\e'\[*([0-9;])m/}"
Make it a function:
# Strips ANSI codes from text
# 1: The text
# >: The ANSI stripped text
function strip_ansi() {
shopt -s extglob # function uses extended globbing
printf %s "${1//$'\e'\[*([0-9;])m/}"
}
See:
Bash Shell Parameter Expansion
Bash Pattern-Matching

bash: syntax error near unexpected token `else' formatting issue?

I'm pretty new to bash and am having trouble with what I assume is formatting. I'm trying to edit the /etc/profile so it will display a login message for root and a different login message for anyone else. But I'm getting the error bash: syntax error near unexpected token else. I've tried all the different combinations of no semicolon, then on the next line etc but always get the same error. I've tried the lines separately and they display fine (except $HOSTNAME, can't get that to work). When run like this and login with root it will just jump to "Welcome $USER...".
Anyone suggestions would be appreciated!
if [ "$UID" -ne 0 ]; then
echo -e "\033[40;37;7m Danger! Root is doing stuff in `pwd`\033[0m"
else
echo "Welcome $USER! You are working on `$HOSTNAME` in `pwd`."
fi
As written, above works for me - but, as pointed out, you should change your test to -eq 0.
For the syntax error near unexpected token problem - I will guess that your file contains embedded 'control codes', i.e. most likely a carriage return \r.
Try:
cat -e ~/your_profile
see any non-printable characters? if so, remove them (cat options may vary - check you manpage) or
od -c ~/your_profile

Bash: "command not found" on simple variable assignment

Here's a simple version of my script which displays the failure:
#!/bin/bash
${something:="false"}
${something_else:="blahblah"}
${name:="file.ext"}
echo ${something}
echo ${something_else}
echo ${name}
When I echo the variables, I get the values I put in, but it also emits an error. What am I doing wrong?
Output:
./test.sh: line 3: blahblah: command not found
./test.sh: line 4: file.ext: command not found
false
blahblah
file.ext
The first two lines are being emitted to stderr, while the next three are being output to stdout.
My platform is fedora 15, bash version 4.2.10.
You can add colon:
: ${something:="false"}
: ${something_else:="blahblah"}
: ${name:="file.ext"}
The trick with a ":" (no-operation command) is that, nothing gets executated, but parameters gets expanded. Personally I don't like this syntax, because for people not knowing this trick the code is difficult to understand.
You can use this as an alternative:
something=${something:-"default value"}
or longer, more portable (but IMHO more readable):
[ "$something" ] || something="default value"
Putting a variable on a line by itself will execute the command stored in the variable. That an assignment is being performed at the same time is incidental.
In short, don't do that.
echo ${something:="false"}
echo ${something_else:="blahblah"}
echo ${name:="file.ext"}
It's simply
variable_name=value
If you use $(variable_name:=value} bash substitutes the variable_name if it is set otherwise it uses the default you specified.

Resources