Bash / Escaping quotes is driving me crazy . .
Philip Rhoades
phil at pricom.com.au
Tue Feb 23 01:51:45 UTC 2016
Cameron,
> Date: Tue, 23 Feb 2016 08:39:07 +1100
> From: Cameron Simpson <cs at zip.com.au>
> To: phil at pricom.com.au, Community support for Fedora users
> <users at lists.fedoraproject.org>
> Subject: Re: Bash / Escaping quotes is driving me crazy . .
> Message-ID: <20160222213907.GA79479 at cskk.homeip.net>
> Content-Type: text/plain; charset=us-ascii; format=flowed
>
> On 21Feb2016 14:18, Philip Rhoades <phil at pricom.com.au> wrote:
>>> Date: Sat, 20 Feb 2016 16:40:01 -0800
>>> From: Gordon Messmer <gordon.messmer at gmail.com>
> [...]
>>> On 02/20/2016 04:05 PM, Philip Rhoades wrote:
>>>> . . but why is there only a problem with the "flac" OR? - all three
>>>> files have at least one space in the filename:
>>>
>>> Your mistake seems to be believing that the shell can understand the
>>> way
>>> you're nesting quotes. It can't. Each unescaped quote you're using
>>> simply terminates the quoted string that preceded it. So your
>>> example:
>>>
>>> ssh localhost "find
>>> /home/phil/music/ambient/RobertGass+OnWingsOfSong/OmNamahaShivaya
>>> -maxdepth 1 -type f \\( -name "*.mp3" -o -name "*.m4a" -o -name
>>> "*.flac"
>>> \\)"
>
> Because quoting shell commands to dispatch over ssh is tedious, I have
> this
> script:
>
> https://bitbucket.org/cameron_simpson/css/src/tip/bin/sshx
>
> which accepts an unquoted command line and does the work for you, so
> your
> request would be written:
>
> sshx localhost find /your/music/dir -maxdepth 1 -type f \( -name
> \*.mp3 -o -name \*.m4a -o -name \*.flac
>
> i.e. exactly as you would without the leading "ssh localhost" to an
> shell
> prompt.
>
> Please feel free to fetch it (use the "Raw" link at top right) and use
> it. It
> saves a lot of pain.
Excellent! Thanks for that!
> BTW, why "localhost"? Or is this just an example for test, to be used
> on other
> hosts later when working?
Exactly.
>> OK, that all makes sense but there is a further issue - I was trying
>> to keep
>> it simple - this whole line is inside a Ruby "system" command ie:
>>
>> system( "ssh .. " )
>>
>> - so my working version is the same as your first option (without the
>> escaped '*'s) but with an extra '\' at each place. I can't use the
>> second option because I need to use double quotes so that I can use
>> Ruby variables inside the double quotes eg:
>>
>> #{path}
>
> All this means is that you need to take your shell command line, once
> you have
> a working one, and quote it again to express it correctly as a Ruby
> string.
> Bear with me, because I don't yet speak Ruby.
>
> SO first get your shell command line. Make it as simple as feasible. If
> you've
> fetched my "sshx" script, that can be:
>
> sshx localhost find ...
>
> quoted no more than any local "find" command would be. Now express that
> as a
> Ruby string:
>
>
> https://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Literals#Strings
>
> Since you're using double quotes to allow # interpolation (this is not
> a great
> practive for command lines, to which we'll come later) you probably
> want to
> either run with a command line with no double quotes in it and with all
> the
> backslashes doubled:
>
> shcmd = "sshx localhost find /dir -name \\*.mp3 ..."
>
> If _are_ using a command line with double quotes, they can also be
> escaped, eg:
>
> shcmd = "sshx localhost find /dir -name \"*.mp3\" ..."
>
> I'm imagining that you're expecting to replace "localhost" or the
> directory
> with something like #{remotehost} and #{path} later, thus:
>
> shcmd = "sshx #{remotehost} find #{path} -name \"*.mp3\" ..."
>
> and here we come to the issue of quoting yet again. Supposing your path
> has
> spaces or other shell punctuation in it. You also need to quote it,
> _before_
> using it in your command line string.
>
> I find it useful to have a "shell quote" function in my kit, whatever
> the
> language. You can take any string to be used in the shell undamaged and
> suitable quote it by replacing all single quotes with:
>
> '\''
>
> and enclosing the whole result in a pair of single quotes. You would
> need to do
> this for the variable you want to interpolate:
>
> qpath = shqstr(path)
> qremotehost = shqstr(remotehost)
>
> and then interpolate _those_ values:
>
> shcmd = "sshx #{qremotehost} find #{qpath} -name \"*.mp3\" ..."
>
> and finally running:
>
> system(shcmd)
>
> However, it is better to avoid going through the shell at all if it is
> only an
> intermediary to running a single command (your single "ssh" command).
> Using
> system() with a carefully quoted string has all the same fiddliness as
> passing
> carefully constructed SQL to a database query: any tiny flaw in the
> quoting can
> lead to incorrect behaviour, and if any of the strings come from user
> input,
> that is also an avenue for attack (there are known as injection
> attacks, where
> use input is injected into a command you issue, perverting your
> intent):
>
> http://bobby-tables.com/
>
> The URL above uses SQL because it is an incredibly command misuse, but
> htis
> issue applies equally well to shell commands constructed the way you
> are
> constructing them.
>
> To avoid going though the shell at all you want to invoke your command
> with the
> Subprocess Ruby module:
>
> http://www.rubydoc.info/github/stripe/subprocess/Subprocess
>
> Construct your command as a list of strings:
>
> cmd = ['sshx', remotehost, 'find', path, '-name', '*.mp3', ...]
>
> and invoke Subprocess.popen to collect the resulting output. Notice
> that
> there's no quoting there at all, including of things like *.mp3. This
> is
> because the shell will never see these strings, and therefore you need
> only
> present them directly as valid Ruby strings.
Wow! Thanks for taking the time to answer in such useful detail! I
will have a look at all that - interesting stuff!
And thanks to everyone else who helped as well.
Regards,
Phil.
--
Philip Rhoades
PO Box 896
Cowra NSW 2794
Australia
E-mail: phil at pricom.com.au
More information about the users
mailing list