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

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

Related

Bash next line (\) in command causing spacing issues

I have the following function I want to call:
function print_something {
echo "test\
something\
yep"
}
when called it prints:
'test something yep'
I would like it to print:
'testsomethingyep'
I can get this to print if I do:
function print_something {
echo "test\
something\
yep"
}
but I don't think that looks great..
(Root problem is a curl command not an echo)
Consider assembling your pieces an array, and then combining them into a string later. Array definition syntax is far more forgiving -- not requiring backslashes at all, and also allowing comments on each line and between lines.
#!/usr/bin/env bash
# ^^^^- arrays and printf -v require bash, not sh
pieces=(
test # this also lets you use comments
something # and you don't need any backslashes at all!
# one can also have a full-line comment midway through your array
"space here" # plus if you want to add a literal space you can do it
)
printf -v oneword '%s' "${pieces[#]}"
echo "$oneword"
...properly emits:
testsomethingspace here
Here are three ideas:
#!/bin/bash
print_something() {
tr -d \\n <<- EOF
test
something
yep
EOF
echo
}
print_something2() {
echo "test"$(:
)"something"$(:
)"yep"
}
print_something3() {
tr -d \\t <<- EOF
test\
something\
yep
EOF
}
print_something
print_something2
print_something3
The first uses a <<- style heredoc to remove all of the leading indentation (that indentation must be hard-tabs for this to work, and coding styles that mandate the use of spaces for indentation render this solution unusable (this is one reason coding styles that mandate the use of spaces in shell scripts are IMO utterly useless)) and the extra tr to remove the newlines. (The additional echo is then needed to add the trailing newline.). The second uses the $(:) command substitution to discard all the intervening whitespace. The 3rd manually deletes all the hard tabs.

Is it possible to combine bash variable search and replace with substring?

I have this function:
#! /usr/bin/env bash
function underline() {
U="${1//?/${2:--}}"
echo -e "\n$1\n${U:0:${#1}}\n"
}
underline "$1" "^-v-"
will work as expected:
$ ./u.sh "This is all you're going to see"
This is all you're going to see
^-v-^-v-^-v-^-v-^-v-^-v-^-v-^-v
It does what you expect. Originally it assumed that the underline character was just that, 1 character.
For "fun", I extended it to move from a single character underline to a repeating string underline (why? ... well ... because I can I suppose ... almost zero practical value in this exercise!).
And so, being the "let's make this so difficult that you need to write 2 pages of documentation to explain what's going on" sort of guy, I was wondering if the function could be written as a single line. And I don't mean:
function underline() { U="${1//?/${2:--}}"; echo -e "\n$1\n${U:0:${#1}}\n"; }
I don't think you can combine bash's variable search/replace with substring which is what is required.
I'm aware that this will work happily in zsh:
#! /usr/bin/env zsh
function underline() {
echo -e "\n$1\n${${1//?/${2:--}}:0:${#1}}\n"
}
underline $1 "^-v-"
e.g.
$ ./u.sh "This is all you're going to see"
This is all you're going to see
^-v-^-v-^-v-^-v-^-v-^-v-^-v-^-v
but not capable of this in bash (it seems).
No, you cannot combine those in a single parameter expansion in bash. But this single printf (a builtin in bash) should do the trick:
underline() { printf '%s\n%.*s\n' "$1" ${#1} "${1//?/${2:--}}"; }
underline "This is all you're going to see" "^-v-"
outputs
This is all you're going to see
^-v-^-v-^-v-^-v-^-v-^-v-^-v-^-v

How to convert [abc\tdef\tghi] to ["abc" "def" "ghi"] in Mac Terminal?

I have the following variable in Terminal
echo $VAR1
abc def ghi
<- Separated by tab
How can I convert this to
"abc" "def" "ghi"
with single space in-between?
In zsh, you can break your string up into an array of quoted words with
var1=$'abc\tdef\tghi'
words=( "${(qqq)=var1}" )
and then turn it back into a single string if wanted with
var2="${words[*]}"
printf "%s\n" "$var2" # prints "abc" "def" "ghi"
Or to skip the intermediate array if you don't need it:
var2=${(A)${(qqq)=var1}}
Assuming the variable contains actual tab characters (not backslashes followed by "t"), you can replace tabs with " " while expanding the variable (see here, a ways down in the list of modifiers), and also add quotes at the beginning and end, like this:
"\"${VAR1//$'\t'/\" \"}\""
There's a rather complex mix of quoting and escaping modes here. The double-quotes at the very beginning and end make the whole thing a double-quoted string, so the shell doesn't do anything weird to whitespace in it. The various escaped double-quotes in it will all be treated as literal characters (because they're escaped), and just be part of the output. And the pattern string, $'\t', is in ANSI-C quoting mode, so that the \t gets converted to an actual tab character.
Here's a couple of examples of using it:
% VAR1=$'abc\tdef\tghi' # Define a variable with actual tab characters
% echo "\"${VAR1//$'\t'/\" \"}\"" # Pass the converted version to a command
"abc" "def" "ghi"
% VAR2="\"${VAR1//$'\t'/\" \"}\"" # Store converted version in another variable
% echo "$VAR2"
"abc" "def" "ghi"
This could do what you want
echo -e "abc\tdef\tghi\tjhg\tmnb" | sed -ne 's/\t/" "/g; s/.*/"\0"/p'
Result:
"abc" "def" "ghi" "jhg" "mnb"
You may leverage awk. For example:
user$ $var='abc\tdef\tghi'
user$ $echo -e ${var}
(output)>>> abc def ghi
user$ $echo -e ${var} | awk -F '\t' '{ for (i=1; i <NF; i++) {printf "\"%s\" ", $i}; printf "\"%s\"\n", $NF}'
(output)>>> "abc" "def" "ghi"

zsh sed expanding a variable with special characters and keeping them

I'm trying to store a string in a variable, then expand that variable in a sed command.
Several of the values I'm going to put in the variable before calling the command will have parentheses (with and without slashes before the left parentheses, but never before the right), new lines and other special characters. Also, the string will have double quotes around it in the file that's being searched, and I'd like to use those to limit only to the string I'm querying.
The command needs to be able to match with those special characters in the file. Using zsh / Mac OS, although if the command was compatible with bash 4.2 that'd be a nice bonus. echoing to xargs is fine too. Also, if awk would be better for this, I have no requirement to use sed.
Something like...
sed 's/"\"$(echo -E - ${val})\""/"${key}.localized"/g' "${files}"
Given that $val is the variable I described above, $key has no spaces (but underscores) & $files is an array of file paths (preferably compatible with spaces, but not required).
Example Input values for $val...
... "something \(customStringConvertible) here" ...
... "something (notVar) here" ...
... "something %# here" ...
... "something # 100% here" ...
... "something for $100.00" ...
Example Output:
... "some_key".localized ...
I was using the sed command to replace the examples above. The text I'm overwriting it with is pretty straight forward.
The key problem I'm having is getting the command to match with the special characters instead of expanding them and then trying to match.
Thanks in advance for any assistance.
awk is better since it provides functions that work with literal strings:
$ val='something \(customStringConvertible) here' awk 'index($0,ENVIRON["val"])' file
... "something \(customStringConvertible) here" ...
$ val='something for $100.00' awk 'index($0,ENVIRON["val"])' file
... "something for $100.00" ...
The above was run on this input file:
$ cat file
... "something \(customStringConvertible) here" ...
... "something (notVar) here" ...
... "something %# here" ...
... "something # 100% here" ...
... "something for $100.00" ...
With sed you'd have to follow the instructions at Is it possible to escape regex metacharacters reliably with sed to try to fake sed out.
It's not clear what your real goal is so edit your question to provide concise, testable sample input and expected output if you need more help. Having said that, it looks like you're doing a substitution so maybe this is what you want:
$ old='"something for $100.00"' new='here & there' awk '
s=index($0,ENVIRON["old"]) { print substr($0,1,s-1) ENVIRON["new"] substr($0,s+length(ENVIRON["old"])) }
' file
... here & there ...
or if you prefer:
$ old='"something for $100.00"' new='here & there' awk '
BEGIN { old=ENVIRON["old"]; new=ENVIRON["new"]; lgth=length(old) }
s=index($0,old) { print substr($0,1,s-1) new substr($0,s+lgth) }
' file
or:
awk '
BEGIN { old=ARGV[1]; new=ARGV[2]; ARGV[1]=ARGV[2]=""; lgth=length(old) }
s=index($0,old) { print substr($0,1,s-1) new substr($0,s+lgth) }
' '"something for $100.00"' 'here & there' file
... here & there ...
See How do I use shell variables in an awk script? for info on how I'm using ENVIRON[] vs ARGV[] above.

Bash - how to add newlines to string in loop with command substitution?

I have a for loop thats iterating over a list of items, gathering statistics. The constructed string is then being sent as the body of an email using the mail command. My question is: what is the proper way to output newlines, as opposed to escaped \n characters as my script is currently doing?
Here's the relavent bash code:
for x in $([..]); do
BODY="${BODY}\nReport for ${x}: \nSuccess: `get_logs $x 1`\nAttempts: `get_logs $x`"
done;
As I said in the comments, you can use:
echo -e
The -e flag enables the interpretation of backslash escapes
You can use newlines inside quoted strings, the only downside of this is that you cannot indent the new lines, because the indent will be part of the string.
for x in $([..]); do
BODY="${BODY}
Report for ${x}:
Success: $(get_logs $x 1)
Attempts: $(get_logs $x)"
done;
Alternatively, append each line separately to make your code more readable.
for x in $([..]); do
BODY+="Report for ${x}:"$'\n'
BODY+="Success: $(get_logs $x 1)"$'\n'
BODY+="Attempts: $(get_logs $x)"$'\n'
done;
$'\n' is an ANSI C string and will be replaced by a line feed.

Resources