Multiline bash using sed to remove leading whitespace: quotes erroneously preserved in command argument

早过忘川 提交于 2019-12-12 06:36:42

问题


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, after eval 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!