问题
I am running this piece of code:
annotate-output $((sed -E 's/^[ ]+//;' <<____COMMAND
sshfs
foo_user@fooserver.com:/sftp_folder
/var/sshfs.sandbox/server.com
-o
user=foo_user
,reconnect
,ServerAliveInterval=15
,ServerAliveCountMax=3
____COMMAND
) | sed -E -e ':a;N;$!ba;s/\n//g')
It's my (perhaps clumsy) attempt at having a generic way of achieving multiline in bash. Note that there is a trailing space whenever it is needed by the command I'm trying to split into multple lines:
sshfs
foo_user@fooserver.com:/sftp_folder
/var/sshfs.sandbox/server.com
-o
And no trailing spaces for options that have to be concatenated without an intervening blank space:
user=foo_user
,reconnect
,ServerAliveInterval=15
,ServerAliveCountMax=3
It works ok if I don't have quotes or double quotes anywhere. If I run this:
annotate-output $((sed -E 's/^[ ]+//;' <<____COMMAND
sshfs
foo_user@fooserver.com:/sftp_folder
"/var/sshfs.sandbox/server.com"
-o
user=foo_user
,reconnect
,ServerAliveInterval=15
,ServerAliveCountMax=3
____COMMAND
) | sed -E -e ':a;N;$!ba;s/\n//g')
I get:
fuse: bad mount point `"/var/sshfs.sandbox/server.com"': No such file or directory
It looks like the quotes are passed to sshfs as part of the "argument". Why?
Edit 1 (20170418 0739) [note this is now a separate question]:
I understand now why the quotes are preserved (ty Charles Duffy - see his answer below)
I modelled my solution after this answer. I need to apply sed twice, once for the leading space , and another one for the EOL, and that's why I end up using those comprehensions. PS: I have leading spaces, not leading tabs, hence the <<- marker that removes leading tabs is not useful
My original problem is that I want a boilerplate header/footer that I can paste in any script, and then just type the command enclosed in this header footer as if I didn't have to worry about indentation. I know I can solve the problem by assigning to a variable and then using the results later on, but I don't want that. I would like to be able to enter the newlined, indented command only in one place. Basically. with these characteristics:
- End of line spaces before the actual EOL preserved
- End of lines removed
- New line leading spaces removed
Is this possible?
Edit 2 (20170421 0818): The original question has been answered. Please follow the evolution of the related problem I'm trying to solve here
回答1:
An approach that actually works
IFS=, # cause "${array[*]}" to combine all items with commas
options=(
user=foo_user # can put a comment here if you like
reconnect # or here
ServerAliveInterval=15 # or so forth
ServerAliveCountMax=3
)
sshfs_cmd=(
sshfs
foo_user@fooserver.com:/sftp_folder
/var/sshfs.sandbox/server.com
-o "${options[*]}"
)
"${sshfs_cmd[@]}"
Explaining why the other approach doesn't
An unquoted expansion goes through the following parsing steps, and only the following parsing steps:
- String-splitting (splitting into multiple distinct words based on characters in
IFS
) - Glob expansion (replacement of any word which looks like a glob expression with a list of names matching that expression).
That's it. No quoting, no quote removal, no parameter substitution, etc. Without quotes being parsed as syntax, variables they would cause to be parsed as a single word aren't parsed in that manner; and without quote removal, those quotes are present as literal data in the commands being invoked.
See BashFAQ #50 for a detailed discussion.
回答2:
Using eval
The below does what you want -- removing leading but not trailing whitespace, and combining heredoc contents into a single command. (There's no need to go to lengths to preserve a trailing newline at the end of a command: eval "true"
works perfectly well even without true
ending in a newline literal).
eval "$(
{ sed -E -e 's/^[ ]+//;' -e ':a;N;$!ba;s/\n//g' | tr -d '\n'; } <<'____COMMAND'
sshfs
foo_user@fooserver.com:/sftp_folder
/var/sshfs.sandbox/server.com
-o
user=foo_user
,reconnect
,ServerAliveInterval=15
,ServerAliveCountMax=3
____COMMAND
)"
Some items to note:
- The sigil in the heredoc (
____COMMAND
) is quoted. This ensures that expansions only happen later, aftereval
is called, vs earlier (when the heredoc's contents themselves are being expanded). - Multiple commands can be passed to a single
sed
invocation using an invocation of-e
per each.
But Don't. Seriously.
Giving semantic meaning to trailing whitespace is an easy way to confuse anyone else reading your code. Just as tabs-vs-space questions have concerned generations of developers working with Makefiles or with Python (before PEP-8 standardized on spaces only), having trailing-space vs no-trailing-space be a semantically meaningful distinction is going to seriously confuse anyone reading your code, and will lead to conflicts with automated format validation (such as functionality included in current releases of git
that emit warnings when any line contains trailing whitespace) in the future.
来源:https://stackoverflow.com/questions/43459098/multiline-bash-using-sed-to-remove-leading-whitespace-quotes-erroneously-preser