Cameron,
Date: Tue, 23 Feb 2016 08:39:07 +1100
From: Cameron Simpson <cs@xxxxxxxxxx>
To: phil@xxxxxxxxxxxxx, Community support for Fedora users
<users@xxxxxxxxxxxxxxxxxxxxxxx>
Subject: Re: Bash / Escaping quotes is driving me crazy . .
Message-ID: <20160222213907.GA79479@xxxxxxxxxxxxxxx>
Content-Type: text/plain; charset=us-ascii; format=flowed
On 21Feb2016 14:18, Philip Rhoades <phil@xxxxxxxxxxxxx> wrote:
Date: Sat, 20 Feb 2016 16:40:01 -0800
From: Gordon Messmer <gordon.messmer@xxxxxxxxx>
[...]
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@xxxxxxxxxxxxx
--
users mailing list
users@xxxxxxxxxxxxxxxxxxxxxxx
To unsubscribe or change subscription options:
https://admin.fedoraproject.org/mailman/listinfo/users
Fedora Code of Conduct: http://fedoraproject.org/code-of-conduct
Guidelines: http://fedoraproject.org/wiki/Mailing_list_guidelines
Have a question? Ask away: http://ask.fedoraproject.org