5 ms·
FWIW Oils has an option to prevent the ambiguity: osh-0.37$ echo "c:\new" c:\new osh-0.37$ shopt --set no_parse_backslash osh-0.37$ echo "c:\n
by chubot 3mo ago
FWIW Oils has an option to prevent the ambiguity:
osh-0.37$ echo "c:\new"
c:\new
osh-0.37$ shopt --set no_parse_backslash
osh-0.37$ echo "c:\new"
echo "c:\new"
^
[ interactive ]:6: Invalid char escape in double quoted string (OILS-ERR-12)
It really should be
echo "c:\\new" # with two backslashes
That is an unambiguous program that works in every shell. In a well-written shell program, the only things that should follow a single backslash in a double quoted string are
\ " ` $
(Although I found that this option is only on in ysh, not in shopt --set strict:all ... arguably that should be changed)
Nine Reasons to Use OSH - https://oils.pub/osh.html https://oils.pub/osh.html
- gaigalas 3mo agoyash also has something similar, ECHO_STYLE var. https://magicant.github.io/yash/doc/_echo.html https://magicant.github.io/yash/doc/_echo.html Additionally, the ksh family has `print`, which solves some of the issues. And the story repeats all over again, each shell solving the problem in a different way :) That's one of the main reasons I went for solving the portability problem from the scripting side, not the interpreter side.
- dzaima 3mo agoThat seems to be be an entirely-different question - `echo "c:\\new"` still differs in behavior between bash and dash - dash parses backslashes in both the double-quoted string, and then echo does another backslash parsing pass, still printing a newline; whereas bash prints a backslash + n.
- chubot 3mo agoOK interesting, yeah I think dash is just plain broken ... $ dash -c 'echo "c:\\new"' c: ew $ busybox ash -c 'echo "c:\\new"' c:\new It requires 4 backslashes: $ dash -c 'echo "c:\\\\new"' c:\new $ busybox ash -c 'echo "c:\\\\new"' c:\\new I am not sure this is a matter of "undefined behavior in POSIX" -- I think it might just be dash being wildly unconformant, which I have seen in other cases. It's one of the least POSIX compliant shells. It is derived from the same codebase as busybox ash, but busybox receives more maintenance. --- In any case, it is pretty sad that sh is in such poor shape than the default /bin/sh on Debian has different behavior in this basic case. I built OSH to be a set of semantics agreed upon by many shells. I don't think any cross-shell test suites like our spec tests had existed in the past - https://oils.pub/release/0.37.0/quality.html https://oils.pub/release/0.37.0/quality.html But there is little coordination among shell authors, and no real motivation to fix the gaps. In contrast, there is A LOT of coordination among JavaScript engine authors, mostly because there are people paid to work on them.
- dzaima 3mo agoOP links you to POSIX explicitly denoting it being implementation-defined - https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_05 https://pubs.opengroup.org/onlinepubs/9699919799/utilities/e..., and https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 https://pubs.opengroup.org/onlinepubs/9699919799/utilities/e... literally says "It is not possible to use echo portably across all POSIX systems unless [specific setup]" Interestingly, that doesn't allow implementation-defined behavior for "echo -e", for which bash does have special behavior.
- chubot 3mo agoOK interesting, I knew about the -e -n flags issue, which means that any portable shell script has to use printf, not echo. Didn't quite realize that the use of backslash also forces printf ... what a mess
- gaigalas 3mo agoHere's how I deal with it using feature detection: case "$({ printf %b "\\061" || print -r -- 2 || echo -n -e "\\063" || command -p printf %b "\\064"; } 2>/dev/null)" in 1) alias _printb1='printf %b' alias _printn1='printf %s' ;; 2) alias _printb1='print -n --' alias _printn1='print -nr --' ;; 3) alias _printb1='echo -n -e' alias _printn1='echo -n -E' ;; 4) _printb1 () { command -p printf '%b' "$1"; } _printn1 () { command -p printf '%s' "$1"; } ;; '-e 3') _pdash () { _pd=$1; while :; do case $_pd in -*) echo -n "\\055"; _pd=${_pd#-};; *) break;; esac; done; } _printb1 () { _pdash "$1"; echo -n "$_pd"; } _printn1 () { local _p _r _pdash "$1"; _r=$_pd while case $_r in *"$_BS"*) :;; *) false;; esac; do _p=${_r%%"$_BS"*} _r=${_r#*"$_BS"} echo -n "$_p$_BS$_BS" done _pdash "$_r"; _r=$_pd echo -n "$_r" } ;; *) exit 1;; esac 1) is for shells who have printf, 2) is for the ksh family, 4) is for PATH='' yash exclusively, -e 3) is for posh exclusively. It exports two callables, _printb1 and _printf1 that escape and don't escape backslashes respectively, are immune to trailing dash gotchas and other pitfalls. Unfortunatelly, not all shells allow overriding `echo` so I can't proper polyfill when necessary. I'm going to extremes here, but a simpler version of this can be used for the most popular shells.