Here's a barebones Python app that simply prints the command-line arguments passed in:
import sys
if __name__ == "__main__":
print "Arguments:"
for i in range(len(sys.argv)):
print "[%s] = %s" % (i, sys.argv[i])
And here's some sample runs:
python args.py hello world
Arguments:
[0] = args.py
[1] = hello
[2] = world
python args.py "hello world"
Arguments:
[0] = args.py
[1] = hello world
python args.py "hello\world"
Arguments:
[0] = args.py
[1] = hello\world
So far so good. But now when I end any argument with a backslash, Python chokes on it:
python args.py "hello\world\"
Arguments:
[0] = args.py
[1] = hello\world"
python args.py "hello\" world "any cpu"
Arguments:
[0] = args.py
[1] = hello" world any
[2] = cpu
I'm aware of Python's less-than-ideal raw string behavior via the "r" prefix (link), and it seems clear that it's applying the same behavior here.
But in this case, I don't have control of what arguments are passed to me, and I can't enforce that the arguments don't end in a backslash. How can I work around this frustrating limitation?
--
Edit: Thanks to those who pointed out that this behavior isn't specific to Python. It seems to be standard shell behavior (at least on Windows, I don't have a Mac at the moment).
Updated question: How can I accept args ending in a backslash? For example, one of the arguments to my app is a file path. I can't enforce that the client sends it to me without a trailing backslash, or with the backslash escaped. Is this possible in any way?
That's likely the shell treating \ as an escape character, and thus escaping the character. So the shell sends \" as " (because it thinks you are trying to escape the double quote). The solution is to escape the escape character, like so: $ python args.py "hello\world\\".
The Microsoft Parameter Parsing Rules
These are the rules for parsing a command line passed by CreateProcess() to a program written in C/C++:
Parameters are always separated by a
space or tab (multiple spaces/tabs
OK)
If the parameter does not contain
any spaces, tabs, or double quotes,
then all the characters in the
parameter are accepted as is (there
is no need to enclose the parameter
in double quotes).
Enclose spaces and tabs in a double
quoted part
A double quoted part can be anywhere
within a parameter
2n backslashes followed by a "
produce n backslashes + start/end
double quoted part
2n+1 backslashes followed by a "
produce n backslashes + a literal
quotation mark
n backslashes not followed by a
quotation mark produce n backslashes
If a closing " is followed
immediately by another ", the 2nd "
is accepted literally and added to
the parameter (This is the
undocumented rule.)
For a detailed and clear description see http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESDOC
The backslash at the end is interpreted as the start of an escape sequence, in this case a literal double quote character. I had a similar problem with handling environment parameters containing a path that sometimes ended with a \ and sometimes didn't.
The solution I came up with was to always insert a space at the end of the path string when calling the executable. My executable then uses the directory path with the slash and a space at the end, which gets ignored. You could possibly trim the path within the program if it causes you issues.
If %SlashPath% = "hello\"
python args.py "%SlashPath% " world "any cpu"
Arguments:
[0] = args.py
[1] = hello\
[2] = world
[3] = any cpu
If %SlashPath% = "hello"
python args.py "%SlashPath% " world "any cpu"
Arguments:
[0] = args.py
[1] = hello
[2] = world
[3] = any cpu
Hopefully this will give you some ideas of how to get around your problem.
The backslash 'escapes' the character following it. This means that the closing quotation marks become a part of the argument, and don't actually terminate the string.
This is the behaviour of the shell you're using (presumably bash or similar), not Python (although you can escape characters within Python strings, too).
The solution is to escape the backslashes:
python args.py "hello\world\\"
Your Python script should then function as you expect it to.
The backslash (\) is escaping the ". That's all. That is how it is supposed to work.
If this is on Windows, then you are not using a standard Windows command prompt (or shell). This must be bash doing this. The Windows command prompt doesn't treat backslash as an escape character (since it's the file path separator).
Extra trivia point: the quoting character in Windows command prompts is caret: ^
When the user passes your function a string "hello\", regardless of what their intention was, they sent the actual string hello", just like if a user passed a filepath like "temp\table" what they have really typed, intentionally or not, is "temp able" (tab in the middle).
This being said, a solution to this problem means that if a user inputs "temp\table" and honestly means "temp able", you are going to process this into "temp\table" and now you've programmatically destroyed the users input.
With this warning in mind, if you still want to do this, you can look for the string representation of these escaped-characters and replace them. As a really easy example, something like this:
def allow_tabs(str_w_tab):
str_w_tab.replace('\t','\\t')
print str_w_tab
Now if you want to handle all the other escape characters, you'll have to do something similar for each one. As for being able to do this for the example: "hello\", the user passed you the string hello", and whether they intended to or not, they never closed the double-quote, so this is what your program sees.
On 'nix based systems, this is a fundamental shell limitation, as others have said here. So, just suck it up. That said, it's really not that important because you don't often need backslashes in arguments on those platforms.
On Windows, however, backslashes are of critical value! A path ending in one would explicitly denote a directory vs a file. I have seen the documentation for MS C (see: https://learn.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85) ), and within the Python source (e.g. in subprocess.list2cmd https://github.com/python/cpython/blob/master/Lib/subprocess.py), explaining this problem with quoting a process argument and have it not able to end with a backslash. So, I forgive the Python developers for keeping the logic the same - but not the MS C ones! This is not a cmd.exe shell issue or a universal limitation for arguments in Windows! (The caret ^ is the equivalent escape character in that natural shell.)
Batch Example (test.bat):
#echo off
echo 0: %0
echo 1: %1
echo 2: %2
echo 3: %3
Now execute it (via cmd.exe):
test.bat -t "C:\test\this path\" -v
Yields:
0: test.bat
1: -t
2: "C:\test\this path\"
3: -v
As you can see - a simple batch file implicitly understands what we want!
But... let's see what happens in Python, when using the standard argparse module (https://docs.python.org/3/library/argparse.html), which is intertwined with sys.argv initial parsing by default:
broken_args.py
import os
import argparse # pip install argparse
parser = argparse.ArgumentParser( epilog="DEMO HELP EPILOG" )
parser.add_argument( '-v', '--verbose', default=False, action='store_true',
help='enable verbose output' )
parser.add_argument( '-t', '--target', default=None,
help='target directory' )
args = parser.parse_args()
print( "verbose: %s" % (args.verbose,) )
print( "target: %s" % (os.path.normpath( args.target ),) )
Test that:
python broken_args.py -t "C:\test\this path\" -v
Yields these bad results:
verbose: False
target: C:\test\this path" -v
And so, here's how I solved this. The key "trick" is first fetching the full, raw command line for the process via the Windows api:
fixed_args.py
import sys, os, shlex
import argparse # pip install argparse
IS_WINDOWS = sys.platform.startswith( 'win' )
IS_FROZEN = getattr( sys, 'frozen', False )
class CustomArgumentParser( argparse.ArgumentParser ):
if IS_WINDOWS:
# override
def parse_args( self ):
def rawCommandLine():
from ctypes.wintypes import LPWSTR
from ctypes import windll
Kernel32 = windll.Kernel32
GetCommandLineW = Kernel32.GetCommandLineW
GetCommandLineW.argtypes = ()
GetCommandLineW.restype = LPWSTR
return GetCommandLineW()
NIX_PATH_SEP = '/'
commandLine = rawCommandLine().replace( os.sep, NIX_PATH_SEP )
skipArgCount = 1 if IS_FROZEN else 2
args = shlex.split( commandLine )[skipArgCount:]
return argparse.ArgumentParser.parse_args( self, args )
parser = CustomArgumentParser( epilog="DEMO HELP EPILOG" )
parser.add_argument( '-v', '--verbose', default=False, action='store_true',
help='enable verbose output' )
parser.add_argument( '-t', '--target', default=None,
help='target directory' )
args = parser.parse_args()
print( "verbose: %s" % (args.verbose,) )
print( "target: %s" % (os.path.normpath( args.target ),) )
Confirm the fix:
python fixed_args.py -t "C:\test\this path\" -v
Yields these good results:
verbose: True
target: C:\test\this path
Related
Need help in fixing this bash script to set a variable with a value including double quotes. Somehow I am defining this incorrectly as my values foo and bar are not enclosed in double quotes as needed.
My script thus far:
#!/usr/local/bin/bash
set -e
set -x
host='127.0.0.1'
db='mydev'
_account="foo"
_profile="bar"
_version=$1
_mongo=$(which mongo);
exp="db.profile_versions_20170420.find({account:${_account}, profile:${_profile}, version:${_version}}).pretty();";
${_mongo} ${host}/${db} --eval "$exp"
set +x
Output shows:
+ host=127.0.0.1
+ db=mydev
+ _account=foo
+ _profile=bar
+ _version=201704112004
++ which mongo
+ _mongo=/usr/local/bin/mongo
+ exp='db.profile_versions_20170420.find({account:foo, profile:bar, version:201704112004}).pretty();'
+ /usr/local/bin/mongo 127.0.0.1/mydev --eval 'db.profile_versions_20170420.find({account:foo, profile:bar, version:201704112004}).pretty();'
MongoDB shell version: 3.2.4
connecting to: 127.0.0.1/mydev
2017-04-22T15:32:55.012-0700 E QUERY [thread1] ReferenceError: foo is not defined :
#(shell eval):1:36
What i need is account:"foo", profile:"bar" to be enclosed in double quotes.
In bash (and other POSIX shells), the following 2 states are equivalent:
_account=foo
_account="foo"
What you want to do is to preserve the quotations, therefore you can do the following:
_account='"foo"'
Since part of what you're doing here is forming JSON, consider using jq -- which will guarantee that it's well-formed, no matter what the values are.
host='127.0.0.1'
db='mydev'
_account="foo"
_profile="bar"
_version=$1
json=$(jq -n --arg account "$_account" --arg profile "$_profile" --arg version "$_version" \
'{$account, $profile, version: $version | tonumber}')
exp="db.profile_versions_20170420.find($json).pretty();"
mongo "${host}/${db}" --eval "$exp"
This makes jq responsible for adding literal quotes where appropriate, and will avoid attempted injection attacks (for instance, via a version passed in $1 containing something like 1, "other_argument": "malicious_value"), by replacing any literal " in a string with \"; a literal newline with \n, etc -- or, with the | tonumber conversion, failing outright with any non-numeric value.
Note that some of the syntax above requires jq 1.5 -- if you have 1.4 or prior, you'll want to write {account: $account, profile: $profile} instead of being able to write {$account, $profile} with the key names inferred from the variable names.
When you need to use double quotes inside a double quoted string, escape them with backslashes:
$ foo="acount:\"foo\"" sh -c 'echo $foo'
acount:"foo"
I needed to enquote something already in a variable and stick that in a variable. Expanding on Robert Seaman's answer, I found this worked:
VAR='"'$1'"'
(single quote, double quote, single quote, variable,single quote, double quote, single quote)
I have the following code in test.rb:
require 'open3'
cmd = 'C:\Program Files\foo\bar.exe'
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
puts "stdout: #{stdout.read}"
puts "\n\n"
puts "stderr: #{stderr.read}"
end
bar.exe is a console application that I created, located in C:\Program Files\foo\. When I run bar.exe:
it outputs "Hello world!"
with any argument, like bar.exe /blah, it outputs a help message.
When I run ruby test.rb I get this error:
C:\RailsInstaller/Ruby2.2.0/lib/ruby/2.2.0/open3.rb:193:in 'spawn': No such file or directory - C:\Program Files\foo\bar.exe (Errno::ENOENT)
from C:\RailsInstaller/Ruby2.2.0/lib/ruby/2.2.0/open3.rb:193:in 'popen_run'
from C:\RailsInstaller/Ruby2.2.0/lib/ruby/2.2.0/open3.rb:193:in 'popen3'
from test.rb:3:in '<main>'
If I change the code to call popen3:
Open3.popen3(cmd, '')
I don't get the Errno::ENOENT error, instead I get the help message, but I want the "Hello World" output.
I searched for a solution but nothing is working, including the answer to "Why does Open3.popen3 return wrong error when executable is missing?".
Why am I getting this error and how do I solve it?
Meditate on this:
cmd = "\P\f\b"
cmd.size # => 3
cmd.chars # => ["P", "\f", "\b"]
cmd.chars.map(&:ord) # => [80, 12, 8]
cmd = "\\P\\f\\b"
cmd.size # => 6
cmd.chars # => ["\\", "P", "\\", "f", "\\", "b"]
cmd.chars.map(&:ord) # => [92, 80, 92, 102, 92, 98]
cmd = '\P\f\b'
cmd.size # => 6
cmd.chars # => ["\\", "P", "\\", "f", "\\", "b"]
cmd.chars.map(&:ord) # => [92, 80, 92, 102, 92, 98]
You're using a double-quoted string with single backslashes as path/directory separators as in the first example. The single back-slashed \f and \b are escaped characters in a double-quoted string, and are not recognized as they were typed using \ f or \ b.
You have two ways of dealing with this, either escaping the backslashes as in the second example, or by using a single-quoted string, as in the third example. It's considered messy to use the second means so use the last for readability and easier maintenance. You get the same characters with less visual noise. This is applicable to string use in most langauges.
The second thing to know is that Ruby doesn't need reverse-slashes as path delimiters. The IO documentation says:
Ruby will convert pathnames between different operating system conventions if possible. For instance, on a Windows system the filename "/gumby/ruby/test.rb" will be opened as "\gumby\ruby\test.rb". When specifying a Windows-style filename in a Ruby string, remember to escape the backslashes:
"c:\\gumby\\ruby\\test.rb"
Our examples here will use the Unix-style forward slashes; File::ALT_SEPARATOR can be used to get the platform-specific separator character.
Finally, you should look at Ruby's Shell and Shellwords in the STDLib. They're your friends.
You are having trouble because "Program Files" is a folder with a space in it. Whenever that happens, you need to double quote it, just as you would on a cmd.exe prompt. And when you're double-quoting, you must remember that your backslash character "\" is an escape character, so you have to double-backslash to get the proper folder separators for Windows. I'm going to use code which actually returns something in my environment; adjust it to your taste. So your code should look like:
require 'open3'
cmd = "\"C:\\Program Files\\Git\\bin\\git.exe\""
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
puts "stdout: #{stdout.read}"
puts "\n\n"
puts "stderr: #{stderr.read}"
end
If you have command line parameters to pass to git, you'd do it like this:
require 'open3'
cmd = "\"C:\\Program Files\\Git\\bin\\git.exe\" --version"
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
puts "stdout: #{stdout.read}"
puts "\n\n"
puts "stderr: #{stderr.read}"
end
Use Open3.popen3([bin, bin]) to prevent shell command handling for a single argument to popen3 (and related methods like spawn).
As you noticed, multiple args can be passed normally without invoking a shell (e.g.
Open3.popen3(bin, arg1, arg2)).
Lua's io.popen is useful for capturing stdout of a command, but it relies on /bin/sh for word-splitting, glob expansion, etc.
So I can do the following and it works great
-- this unfortunately uses /bin/sh
local command = "ls -1";
local ph = io.popen(command, "r")
while true do
local line = ph:read()
if line == nil then
break
end
io.write("I read a line! " .. line .. "\n")
end
io.write("Done with line reading!\n")
Is there some function that's a thin wrapper around spawn* or fork/exec* that I can pass {"ls", "-1"} to in order to avoid the shell?
I'd like to print a string literal in AWK / gawk using the PowerShell command line (the specific program is unimportant). However, I think I misunderstand the quoting rules somewhere along the line -- PowerShell apparently removes double quotes inside single quotes for native commands, but not when passing them to commandlets.
This works in Bash:
bash$ awk 'BEGIN {print "hello"}'
hello <-- GOOD
And this works in PowerShell -- but importantly I have no idea why the escaping is needed:
PS> awk 'BEGIN {print \"hello\"}'
hello <-- GOOD
This prints nothing in PowerShell:
PS> awk 'BEGIN {print "hello"}'
<-- NOTHING IS BAD
If this really is the only way of doing this in PowerShell, then I'd like to understand the chain of quoting rules that explains why. According to the PowerShell quoting rules at About Quoting Rules, this shouldn't be necessary.
BEGIN SOLUTION
The punchline, courtesy of Duncan below, is that you should add this function to your PowerShell profile:
filter Run-Native($command) { $_ | & $command ($args -replace'(\\*)"','$1$1\"') }
Or specifically for AWK:
filter awk { $_ | gawk.exe ($args -replace'(\\*)"','$1$1\"') }
END SOLUTION
The quotes are properly passed to PowerShell's echo:
PS> echo '"hello"'
"hello" <-- GOOD
But when calling out to an external "native" program, the quotes disappear:
PS> c:\cygwin\bin\echo.exe '"hello"'
hello <-- BAD, POWERSHELL REMOVED THE QUOTES
Here's an even cleaner example, in case you're concerned that Cygwin might have something to do with this:
echo #"
>>> // program guaranteed not to interfere with command line parsing
>>> public class Program
>>> {
>>> public static void Main(string[] args)
>>> {
>>> System.Console.WriteLine(args[0]);
>>> }
>>> }
>>> "# > Program.cs
csc.exe Program.cs
.\Program.exe '"hello"'
hello <-- BAD, POWERSHELL REMOVED THE QUOTES
DEPRECATED EXAMPLE for passing to cmd, which does its own parsing (see Etan's comment below):
PS> cmd /c 'echo "hello"'
"hello" <-- GOOD
DEPRECATED EXAMPLE for passing to Bash, which does its own parsing (see Etan's comment below):
PS> bash -c 'echo "hello"'
hello <-- BAD, WHERE DID THE QUOTES GO
Any solutions, more elegant workarounds, or explanations?
The problem here is that the Windows standard C runtime strips unescaped double quotes out of arguments when parsing the command line. PowerShell passes arguments to native commands by putting double quotes around the arguments, but it doesn't escape any double quotes that are contained in the arguments.
Here's a test program that prints out the arguments it was given using the C stdlib, the 'raw' command line from Windows, and the Windows command line processing (which seems to behave identically to the stdlib):
C:\Temp> type t.c
#include <stdio.h>
#include <windows.h>
#include <ShellAPI.h>
int main(int argc,char **argv){
int i;
for(i=0; i < argc; i++) {
printf("Arg[%d]: %s\n", i, argv[i]);
}
LPWSTR *szArglist;
LPWSTR cmdLine = GetCommandLineW();
wprintf(L"Command Line: %s\n", cmdLine);
int nArgs;
szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
if( NULL == szArglist )
{
wprintf(L"CommandLineToArgvW failed\n");
return 0;
}
else for( i=0; i<nArgs; i++) printf("%d: %ws\n", i, szArglist[i]);
// Free memory allocated for CommandLineToArgvW arguments.
LocalFree(szArglist);
return 0;
}
C:\Temp>cl t.c "C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x86\shell32.lib"
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
t.c
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:t.exe
t.obj
"C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x86\shell32.lib"
Running this in cmd we can see that all unescaped quotes are stripped, and spaces only separate arguments when there have been an even number of unescaped quotes:
C:\Temp>t "a"b" "\"escaped\""
Arg[0]: t
Arg[1]: ab "escaped"
Command Line: t "a"b" "\"escaped\""
0: t
1: ab "escaped"
C:\Temp>t "a"b c"d e"
Arg[0]: t
Arg[1]: ab
Arg[2]: cd e
Command Line: t "a"b c"d e"
0: t
1: ab
2: cd e
PowerShell behaves a bit differently:
C:\Temp>powershell
Windows PowerShell
Copyright (C) 2012 Microsoft Corporation. All rights reserved.
C:\Temp> .\t 'a"b'
Arg[0]: C:\Temp\t.exe
Arg[1]: ab
Command Line: "C:\Temp\t.exe" a"b
0: C:\Temp\t.exe
1: ab
C:\Temp> $a = "string with `"double quotes`""
C:\Temp> $a
string with "double quotes"
C:\Temp> .\t $a nospaces
Arg[0]: C:\Temp\t.exe
Arg[1]: string with double
Arg[2]: quotes
Arg[3]: nospaces
Command Line: "C:\Temp\t.exe" "string with "double quotes"" nospaces
0: C:\Temp\t.exe
1: string with double
2: quotes
3: nospaces
In PowerShell, any argument that contains spaces is enclosed in double quotes. Also the command itself gets quotes even when there aren't any spaces. Other arguments aren't quoted even if they include punctuation such as double quotes, and and I think this is a bug PowerShell doesn't escape any double quotes that appear inside the arguments.
In case you're wondering (I was), PowerShell doesn't even bother to quote arguments that contain newlines, but neither does the argument processing consider newlines as whitespace:
C:\Temp> $a = #"
>> a
>> b
>> "#
>>
C:\Temp> .\t $a
Arg[0]: C:\Temp\t.exe
Arg[1]: a
b
Command Line: "C:\Temp\t.exe" a
b
0: C:\Temp\t.exe
1: a
b
The only option since PowerShell doesn't escape the quotes for you seems to be to do it yourself:
C:\Temp> .\t 'BEGIN {print "hello"}'.replace('"','\"')
Arg[0]: C:\Temp\t.exe
Arg[1]: BEGIN {print "hello"}
Command Line: "C:\Temp\t.exe" "BEGIN {print \"hello\"}"
0: C:\Temp\t.exe
1: BEGIN {print "hello"}
To avoid doing that every time, you can define a simple function:
C:\Temp> function run-native($command) { & $command $args.replace('\','\\').replace('"','\"') }
C:\Temp> run-native .\t 'BEGIN {print "hello"}' 'And "another"'
Arg[0]: C:\Temp\t.exe
Arg[1]: BEGIN {print "hello"}
Arg[2]: And "another"
Command Line: "C:\Temp\t.exe" "BEGIN {print \"hello\"}" "And \"another\""
0: C:\Temp\t.exe
1: BEGIN {print "hello"}
2: And "another"
N.B. You have to escape backslashes as well as double quotes otherwise this doesn't work (this doesn't work, see further edit below):
C:\Temp> run-native .\t 'BEGIN {print "hello"}' 'And \"another\"'
Arg[0]: C:\Temp\t.exe
Arg[1]: BEGIN {print "hello"}
Arg[2]: And \"another\"
Command Line: "C:\Temp\t.exe" "B EGIN {print \"hello\"}" "And \\\"another\\\""
0: C:\Temp\t.exe
1: BEGIN {print "hello"}
2: And \"another\"
Another edit: Backslash and quote handling in the Microsoft universe is even weirder than I realised. Eventually I had to go and read the C stdlib sources to find out how they interpret backslashes and quotes:
/* Rules: 2N backslashes + " ==> N backslashes and begin/end quote
2N+1 backslashes + " ==> N backslashes + literal "
N backslashes ==> N backslashes */
So that means run-native should be:
function run-native($command) { & $command ($args -replace'(\\*)"','$1$1\"') }
and all backslashes and quotes will survive the command line processing. Or if you want to run a specific command:
filter awk() { $_ | awk.exe ($args -replace'(\\*)"','$1$1\"') }
(Updated following #jhclark's comment: it needs to be a filter to allow piping into stdin.)
You get different behavior, because you're using 4 different echo commands, and in different ways on top of that.
PS> echo '"hello"'
"hello"
echo is PowerShell's Write-Output cmdlet.
This works, because the cmdlet takes the given argument string (the text within the outer set of quotes, i.e. "hello") and prints that string to the success output stream.
PS> c:\cygwin\bin\echo '"hello"'
hello
echo is Cygwin's echo.exe.
This doesn't work, because the double quotes are removed from the argument string (the text within the outer set of quotes, i.e. "hello") when PowerShell calls the external command.
You get the same result if for instance you call echo.vbs '"hello"' with WScript.Echo WScript.Arguments(0) being the content of echo.vbs.
PS> cmd /c 'echo "hello"'
"hello"
echo is CMD's built-in echo command.
This works, because the command string (the text within the outer set of quotes, i.e. echo "hello") is run in CMD, and the built-in echo command preserves the argument's double quotes (running echo "hello" in CMD produces "hello").
PS> bash -c 'echo "hello"'
hello
echo is bash's built-in echo command.
This doesn't work, because the command string (the text within the outer set of quotes, i.e. echo "hello") is run in bash.exe, and its built-in echo command does not preserve the argument's double quotes (running echo "hello" in bash produces hello).
If you want Cygwin's echo to print outer double quotes you need to add an escaped pair of double quotes to your string:
PS> c:\cygwin\bin\echo '"\"hello\""'
"hello"
I would've expected this to work for the bash-builtin echo es well, but for some reason it doesn't:
PS> bash -c 'echo "\"hello\""'
hello
Quoting rules can get confusing when you're calling commands directly from PowerShell. Instead, I regularly recommend that people use the Start-Process cmdlet, along with its -ArgumentList parameter.
Start-Process -Wait -FilePath awk.exe -ArgumentList 'BEING {print "Hello"}' -RedirectStandardOutput ('{0}\awk.log' -f $env:USERPROFILE);
I don't have awk.exe (does that come from Cygwin?), but that line should work for you.
In puppet, if define command is > 80 characters, how can I wrap into two line to do it?
exec { 'create_domain':
command => "some command exceed 80 character...........................................................how to do how to do?.......",
}
It's sort of ugly, but if the last character in a string is a \ followed by a newline, then the string is continued on the next line. My sample.pp manifest is below:
exec { 'wrapped_string_example':
command => "/bin/echo 12345678901234567890123456789012345678901234567890\
wrapped > /var/tmp/test.txt";
}
Running this with puppet apply sample.pp gives the following output
$ puppet apply sample.pp
notice: /Stage[main]/Exec[wrapped_string_example]/returns: executed successfully
notice: Finished catalog run in 0.10 seconds
And catting the created file shows the lines have wrapped:
$ cat /var/tmp/test.txt
12345678901234567890123456789012345678901234567890wrapped
See https://github.com/puppetlabs/puppet/blob/9fbb36de/lib/puppet/parser/lexer.rb#L537 (as of Puppet v2.7.0)
Also this is sort of a known issue: http://projects.puppetlabs.com/issues/5022
For big chunks of data, heredocs are the best way of dealing with long lines in Puppet manifests. The /L interpolation option is particularly useful. /L causes \ at the end of a line to remove newlines. For example, the following does what you'd expect, stripping indentation and newlines, including the trailing newline.
sshkey { 'example.com':
ensure => present,
type => 'ssh-rsa',
key => #(KEY/L),
RfrXBrU1T6qMNllnhXsJdaud9yBgWWm6OprdEQ3rpkTvCc9kJKH0k8MNfKxeBiGZVsUn435q\
e83opnamtGBz17gUOrzjfmpRuBaDDGmGGTPcO8Dohwz1zYuir93bJmxkNldjogbjAWPfrX10\
8aoDw26K12sK61lOt6GTdR9yjDPdG4zL5G3ZjXCuDyQ6mzcNHdAPPFRQdlRRyCtG2sQWpWan\
3AlYe6h6bG48thlo6vyNvOD8s9K0YBnwl596DJiNCY6EsxnSAhA3Uf9jeKqlVqqrxhEzHufx\
07iP1nXIXCMUV
|-KEY
target => '/home/user/.ssh/authorized_keys',
}
Or to keep the final newline, leave out the - before the end text:
exec { 'create_domain':
command => #(CMD/L),
/bin/echo 123456789012345678901234567890123456789012345678901234567890123456\
wrapped > /var/tmp/test.txt
| CMD
}
As of Puppet 3.5 you have a couple of options that i have used. Ruby allows you to concat strings over a couple of lines.
string = "line #1"\
"line #2"\
"line #3"
p string # => "line #1line #2line #3"
Another option, as of Puppet 3.5 they have added HereDoc functionality. This will allow you to put the string in a section of a source code file that is treated as if it were a separate file.
$mytext = #(EOT)
This block of text is
visibly separated from
everything around it.
| EOT
The puppet documentation is here: https://docs.puppet.com/puppet/4.9/lang_data_string.html#heredocs
If you really care about the 80cols limit you can always abuse a template to achieve that goal
exec {'VeryLongExec':
command => template("${module}/verylongexec")
}
Then put the actual command in that template file
Credits should go to Jan Vansteenkiste to figure