Comparing special characters in Bash - bash

I have a function I have written that outputs coloured output kind of like this:
foo() {
local arrow="\033[1;32m↑\033[0m"
echo "1$arrow"
}
I'd like to test this function so I'm using https://github.com/kward/shunit2 to do unit tests for it.
test_foo() {
local green_arrow="\033[1;32m↑\033[0m"
assertEquals "1$green_arrow" "$(foo)"
}
But shunit complains that: ASSERT:expected:< 1↑> but was:< 1↑>. I'm guessing there's a problem with hidden characters being output by the function.
Is there any way to strip special characters, or escape them from the variable?
Edit:
Running the following commands shows that the \\033 escape character is being converted, probably by echo, into a literal \E escape:
printf '%q' "1$green_arrow"
' 1\\033[1;32m?\206\221\\033[0m'
printf '%q' "$(foo)"
' 1\E[1;32m?\206\221\E[0m'

Using printf -v varname_quoted %q "$varname" to get escaped forms of your content will provide an easier-to-read and easy-to-reason-about form, which can also be included in your code as literals with no additional quoting or escaping needed. (This can be simplified to printf %q "$varname" if your goal is to emit quoted content to stdout, rather than to another variable).
That is to say, to get a format string to a literal (with support in the format string for arguments, ie. %s, %02f, etc):
printf -v green_arrow '\033[1;32m↑\033[0m'
...or, a backslash-escaped string (which supports only a subset of full format-string syntax) to a literal:
printf -v green_arrow '%b' '\033[1;32m↑\033[0m'
...and then, to get that literal emitted in escaped, human-readable form for easy comparison and/or copy-and-paste into shell scripts as a literal:
printf '%q\n' "$green_arrow"
Tying this all together, the following is one way to get a more readable error message (please pardon StackOverflow's syntax highlighting, which as of this writing doesn't fully grok the way nested quoting contexts work in bash):
test_foo() {
local green_arrow=$'\E[1;32m↑\E[0m'
assertEquals "$(printf '%q' " 1$green_arrow")" "$(printf '%q' "$(foo)")"
}
...or, more efficiently (avoiding the unnecessary subshells):
test_foo() {
local green_arrow=$'\E[1;32m↑\E[0m'
local desired_answer_quoted actual_answer_quoted
printf -v desired_answer_quoted '%q' " 1$green_arrow"
printf -v actual_answer_quoted '%q' "$(foo)"
assertEquals "$desired_answer_quoted" "$actual_answer_quoted"
}

Related

How do you convert characters to ASCII without use of the printf in bash

ascii() {printf '%d' "'$1"}
I am currently using this function to convert characters to ASCII, however I just want to store the result of the function as a variable without printing the ascii. How would I go about this? (please bear in mind I have only been using bash for a few hours total, so sorry if this is a dumb question.)
In bash, after
printf -v numval "%d" "'$1"
the variable numval (you can use any other valid variable name) will hold the numerical value of the first character of the string contained in the positional parameter $1.
Alternatively, you can use the command substitution:
numval=$(printf "%d" "'$1")
Note that these still use printf but won't print anything to stdout.
As stated in the comment by #Charles Duffy, the printf -v version is more efficient, but less portable (standard POSIX shell does not support the -v option).
Thx for your script! I didn't know how get ascii values so "'M" rescued me.
I pass parameters to function to get return.
Function returns I use to... Well, return err/status codes.
#!/bin/sh
# posix
ascii () {
# $1 decimal ascii code return
# $2 character
eval $1=$(printf '%d' "'$2")
}
ascii cod 'M'
echo "'M' = $cod"

Print a string with its special characters printed as literal escape sequences

I have a string in a shell/bash script. I want to print the string with all its "special characters" (eg. newlines, tabs, etc.) printed as literal escape sequences (eg. a newline is printed as \n, a tab is printed as \t, and so on).
(Not sure if I'm using the correct terminology; the example should hopefully clarify things.)
Example
The desired output of...
a="foo\t\tbar"
b="foo bar"
print_escape_seq "$a"
print_escape_seq "$b"
...is:
foo\t\tbar
foo\t\tbar
$a and $b are strings that were read in from a text file.
There are two tab characters between foo and bar in the $b variable.
An attempt
This is what I've tried:
#!/bin/sh
print_escape_seq() {
str=$(printf "%q\n" $1)
str=${str/\/\//\/}
echo $str
}
a="foo\t\tbar"
b="foo bar"
print_escape_seq "$a"
print_escape_seq "$b"
The output is:
foo\t\tbar
foo bar
So, it doesn't work for $b.
Is there an entirely straightforward way to accomplish this that I've missed completely?
Bash has a string quoting operation ${var#Q}
Here is some example code
bash_encode () {
esc=${1#Q}
echo "${esc:2:-1}"
}
testval=$(printf "hello\t\tworld")
set | grep "^testval="
echo "The encoded value of testval is" $(bash_encode "$testval")
Here is the output
testval=$'hello\t\tworld'
The encoded value of testval is hello\t\tworld
You will need to create a search and replace pattern for each binary value you wish to replace. Something like this:
#!/bin/bash
esc() {
# space char after //
v=${1// /\\s}
# tab character after //
v=${v// /\\t}
echo $v
}
esc "hello world"
esc "hello world"
This outputs
hello\sworld
hello\tworld
I required something similar for file paths, and I realized that ls -1b does the work, but in the research I found this solution in stackoverflow which is closer to what you were requiring.
Command to escape a string in bash
just compile it with gcc -o "escapify" escapify.c

Bash %s format specifier eliminates spaces in string on print out

I am starting to use the printf instead of echo. My first forray into printf %s is this:
#!/bin/bash
danny=$(tail -1 /come/and/play/with/US.log| ~/walt/convert_gm_est)
printf "%s" $danny
08:02.020ZINFO<-casper/casperbox001wowSYSSTATS[sz=21,tag=0,aux=0]process_log2221397800
It eliminates all the spaces in the string. So I added a space after the format specifier. the string looks nicer with the spaces plus I could rip out the time with awk very easily. I don't see anything about this on the interwebs. I tried a "%s\s" and that did not work. Is this standard procedure for using format specifiers - use a space after %s? Is this the way to do it or am I missing something?
#!/bin/bash
danny=$(tail -1 /come/and/play/with/US.log| ~/walt/convert_gm_est)
printf "%s " $danny
08:02.020Z INFO <- casper/casperbox001wow SYSSTATS[sz=21, tag=0, aux=0] process_log 2221397 80 0 casper#casperbox001wow:~$
When the shell evaluates:
printf "%s" $danny
the shell will expand the value of the variable danny and then split it into words. It will also expand globs in those words. Once that is done, the expression will look something like this (quotes added for clarification):
printf '%s' '08:02.020Z' 'INFO' '<-' 'casper/casperbox001' 'wow0' 'SYSSTATS'...
printf repeats its format string until all of the arguments are consumed. So using the format string %s causes the arguments to be concatenated without intervening spaces.
You probably meant to quote $danny so that it would be presented as a single argument to printf:
printf "%s" "$danny"

How bash eval expansion work for single qoute double qoute

Someone please help to explain how this work? About the single quote it should not interpret anything but it is not working as what i expected. I expect to get echo $testvar value exactly '"123b"'.
a="testvar"
b="'"123b"'"
eval $a='$b'
echo $testvar
'123b'
a="testvar"
b='"123b"'
eval $a='$b'
echo $testvar
"123b"
a="testvar"
b='"123b"'
eval $a=$b
echo $testvar
123b
Guessing that you want testvar to be <single-quote><double-quote>123b<double-quote><single-quote>:
testvar=\'\"123b\"\'
Consider this in C or Java:
char* str = "123b";
printf("%s\n", str);
String str = "123b";
System.out.println(str);
Why does this write 123b when we clearly used double quotes? Why doesn't it write "123b", with quotes?
The answer is that the quotes are not part of the data. The quotes are used by the programming language to determine where strings start and stop, but they're not in any way part of the string. This is just as true for Bash as for C and Java.
Just like there's no way in Java to differentiate Strings created with "123" + "b" and "123b", there's no way in Bash to tell that b='"123b"' used single quotes in its definition, as opposed to e.g. b=\"123b\".
If given a variable you want to assign its value surrounded by single quotes, you can use e.g.
printf -v testvar "'%s'" "$b"
But this just adds new literal single quotes around a string. It doesn't and cannot care how b was originally quoted, because that information is stored.
To instead add a layer of escaping to a variable, so that when evaluated once it turns into a literal string identical to your input, you can use:
printf -v testvar "%q" "$b"
This will produce a value which is quoted equivalently but not necessarily identically to your original definition. For "value" (a literal with double quotes in it), it may produce \"value\" or '"value"' or '"'value'"' which all evaluate exactly to "value".

SPRINTF in shell scripting?

I have an auto-generated file each day that gets called by a shell script.
But, the problem I'm facing is that the auto-generated file has a form of:
FILE_MM_DD.dat
... where MM and DD are 2-digit month and day-of-the-month strings.
I did some research and banged it at on my own, but I don't know how to create these custom strings using only shell scripting.
To be clear, I am aware of the DATE function in Bash, but what I'm looking for is the equivalent of the SPRINTF function in C.
In Bash:
var=$(printf 'FILE=_%s_%s.dat' "$val1" "$val2")
or, the equivalent, and closer to sprintf:
printf -v var 'FILE=_%s_%s.dat' "$val1" "$val2"
If your variables contain decimal values with leading zeros, you can remove the leading zeros:
val1=008; val2=02
var=$(printf 'FILE=_%d_%d.dat' $((10#$val1)) $((10#$val2)))
or
printf -v var 'FILE=_%d_%d.dat' $((10#$val1)) $((10#$val2))
The $((10#$val1)) coerces the value into base 10 so the %d in the format specification doesn't think that "08" is an invalid octal value.
If you're using date (at least for GNU date), you can omit the leading zeros like this:
date '+FILE_%-m_%-d.dat'
For completeness, if you want to add leading zeros, padded to a certain width:
val1=8; val2=2
printf -v var 'FILE=_%04d_%06d.dat' "$val1" "$val2"
or with dynamic widths:
val1=8; val2=2
width1=4; width2=6
printf -v var 'FILE=_%0*d_%0*d.dat' "$width1" "$val1" "$width2" "$val2"
Adding leading zeros is useful for creating values that sort easily and align neatly in columns.
Why not using the printf program from coreutils?
$ printf "FILE_%02d_%02d.dat" 1 2
FILE_01_02.dat
Try:
sprintf() { local stdin; read -d '' -u 0 stdin; printf "$#" "$stdin"; }
Example:
$ echo bar | sprintf "foo %s"
foo bar

Resources