I have been working with multi-line string in bash (no need to bring up bash array, it is a POSIX thing). A full working demo is posted in an online BASH emulator.
Problem I have is that everytime I call a function and return a string back, the proper way to handling multi-line string has inadvertly resulted in the tacking on an extra chr(10) to the end of the string.
Suggested duplicate(s) did not apply:
- How to return a string value from a Bash function - it does not properly deal with empty lines at the end of the multi-line string variable.
- How can I 'echo' out things without a newline? - It does not talk about multi-line non-array string variables.
This working example of a multi-line bash string correctly has a blank line at the end and is:
# bash variable declared as multi-line string
ini_buffer="1.1.1.1"
"
That translate the original multi-line string into a hex dump of length 8:
00000000 31 2e 31 2e 31 2e 31 0a 00 |1.1.1.1..|
00000009
Bash script starts off with:
# there is exactly one chr(10) at the end of ini_buffer
ini_buffer="1.2.3.4
"
echo "initial ini_buffer \"\"\"$ini_buffer\"\"\""
echo "initial ini_buffer len: ${#ini_buffer}"
echo
IFS= read -rd '' result < <(echo "$ini_buffer")
# got a SECOND chr(10) prepended to the final output
echo "result len: ${#result}"
echo "\"\"\"$result\"\"\""
echo
Results are:
initial ini_buffer """1.2.3.4
"""
initial ini_buffer len: 8
result len: 9
"""1.2.3.4
"""
Notice that it grew a character?!
00000000 31 2e 31 2e 31 2e 31 0a 0a 00 |1.1.1.1..|
00000009
Added the first function:
first_function()
{
local first_buffer result1_buffer
# takes a string with a single chr(10)
first_buffer="$1"
# calls a function, which does nothing.
IFS= read -rd '' result1_buffer < <(second_function "$first_buffer")
# yet it got prepended by another chr(10) for a total of two chr(10)
echoerr "result1_buffer len: ${#result1_buffer}"
echoerr "\"\"\"$result1_buffer\"\"\""
# Suuposedly only one way to return a multi-line string neatly,
# and that is via STDOUT (fd=1)
# echo "first_buffer len: ${#first_buffer}"
echo "$result1_buffer"
}
# there is exactly one chr(10) at the end of ini_buffer
ini_buffer="1.2.3.4
"
echo "initial ini_buffer \"\"\"$ini_buffer\"\"\""
echo "initial ini_buffer len: ${#ini_buffer}"
echo
# THIS LINE CHANGED from `echo` to `first_function`
IFS= read -rd '' result < <(first_function "$ini_buffer")
# got a SECOND chr(10) prepended to the final output
# for a total of 3 prepended chr(10)s
echo "result len: ${#result}"
echo "\"\"\"$result\"\"\""
echo
Result of the first function is:
initial ini_buffer """1.2.3.4
"""
initial ini_buffer len: 8
result1_buffer len: 9
"""1.2.3.4
"""
result len: 10
"""1.2.3.4
"""
Every time that a function returning from a nested-called, another chr(10)
gets tacked on to it upon return.
This also got increase when a second function was introduced of which I shall not include here for brevity.
This is getting maddening here to me. Has to do with the last-line being blank (or jus a chr(10) character). Not many online authoritative content on proper handling of multi-line string.
What did I do wrong?
Process substitution (<( ... )
) gets used here instead of the usual command substitution ($( ... )
) which had shown difficulty in working with multi-line string. As a result, I must output any debug stattement to the STDERR using:
echoerr() { printf "%s\n" "$*" >&2; }
I would like to do the proper thing of bash progrmaming with regard to multi-line handling, notably with blank line(s) at the end of its string.
A complete test is reiterated here (working demo link is in cited in the first paragraph):
echoerr() { printf "%s\n" "$*" >&2; }
dump_string_char()
{
local string len_str idx this_char this_int
string="$1"
echoerr "string: \"\"\"$string\"\"\""
len_str="${#string}"
idx=0
while [ $idx -lt ${len_str} ]; do
this_char="${string:$idx:1}"
this_int="$(LC_CTYPE=C printf "%d" "'$this_char")"
echoerr "idx: $idx"
if [ $this_int -lt 32 ]; then
echoerr "$idx: ${this_int}"
else
echoerr "$idx: \"${this_char}\""
fi
((idx++))
done
}
second_function()
{
local second_ini_buffer result2_buffer
second_ini_buffer="$1"
# Some magical awk/sed that did not match any pattern
# So let us use 'echo' to re-save the same string
IFS= read -rd '' result2_buffer < <(echo "$second_ini_buffer")
echoerr "result2_buffer len: ${#result2_buffer}"
echoerr "\"\"\"$result2_buffer\"\"\""
# so pass back the full ini_buffer as-is.
# hopefully with ONE chr(1) at end-of-line.
# but it doesn't.
echo "$result2_buffer"
}
first_function()
{
local first_buffer result1_buffer
# takes a string with a single chr(10)
first_buffer="$1"
# calls a function, which does nothing.
IFS= read -rd '' result1_buffer < <(second_function "$first_buffer")
# IFS= read -rd '' result1_buffer < <(echo "$first_buffer")
# yet it got prepended by another chr(10) for a total of two chr(10)
echoerr "result1_buffer len: ${#result1_buffer}"
echoerr "\"\"\"$result1_buffer\"\"\""
# Suuposedly only one way to return a multi-line string neatly,
# and that is via STDOUT (fd=1)
# echo "first_buffer len: ${#first_buffer}"
echo "$result1_buffer"
}
# there is exactly one chr(10) at the end of ini_buffer
ini_buffer="1.2.3.4
"
echoerr "initial ini_buffer \"\"\"$ini_buffer\"\"\""
echoerr "initial ini_buffer len: ${#ini_buffer}"
echoerr
dump_string_char "$ini_buffer"
IFS= read -rd '' result < <(first_function "$ini_buffer")
# got a SECOND chr(10) prepended to the final output
# for a total of 3 prepended chr(10)s
echoerr "result len: ${#result}"
echoerr "\"\"\"$result\"\"\""
echoerr
Once again to the moderator who thinks these are the same question:
It is not related to a carriage return but many ASCII characters, so this does not apply:
And does not address empty multi-lines like this question does: