Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ chmod +x example.csx
```

## CHANGELOG
### v3.2.1
* **Cmd()** on Windows passes the command to cmd.exe as typed. Every `"` used to arrive as `\"`, so quoted arguments, and any `|`, `&` or `>` inside them, broke (`Cmd("az webapp list --query \"[?name=='x'] | [0]\"")`)
* **Bash()** no longer escapes the command's quotes a second time, so `Bash("printf '%s' \"a b\"")` passes `a b` as one argument

### v3.1.0
* **Cli** now models nested commands, the way `dotnet build` and `dotnet nuget push` do
* `.Command(name, help, c => ...)` gives a verb a command line of its own, and commands nest to any depth
Expand Down
24 changes: 24 additions & 0 deletions Tests/CShell.Tests/Command.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,30 @@ public async Task Test_Bash()
Assert.AreEqual("TestA.txt", result.Trim(), "AsString");
}

[TestMethod]
public async Task Test_Cmd_QuotedArguments()
{
// Quotes, the spaces inside them and a | inside them all reach the program as typed. cmd.exe used to get
// every " as \", which also left the | outside any quotes.
CShell shell = new CShell();
shell.cd(testFolder);
var result = await shell.Cmd("echo x \"[?name=='S'].value | [0]\" y").AsString();

// cmd.exe's echo prints the quotes; bash's drops them.
Assert.AreEqual(IsWindows ? "x \"[?name=='S'].value | [0]\" y" : "x [?name=='S'].value | [0] y", result.Trim());
}

[TestMethod]
public async Task Test_Bash_QuotedArguments()
{
// "a b" is one argument. Bash used to get \"a b\": two arguments, with literal quotes.
CShell shell = new CShell();
shell.cd(testFolder);
var result = await shell.Bash("printf '%s\\n' \"a b\" c").AsString();

Assert.AreEqual("a b\nc", result.Trim().Replace("\r\n", "\n"));
}

[TestMethod]
public async Task Test_Start()
{
Expand Down
25 changes: 12 additions & 13 deletions src/CShell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -346,15 +346,16 @@ public Command Cmd(string cmd)
Console.WriteLine(cmd);
}

List<string> args = new List<string>();
if (!Echo)
// cmd.exe doesn't split its command line the way other programs do: it runs everything after /C as
// typed. Passed as an argument, the command was escaped for argv parsing, so every " reached cmd.exe
// as \" and quoted arguments (and any |, & or > inside them) broke. Hand cmd.exe the command line
// as-is instead; /S strips just the outer pair of quotes and leaves the command's own alone.
var arguments = $"{(Echo ? string.Empty : "/Q ")}/S /C \"{cmd}\"";
return Command.Run("cmd.exe", Array.Empty<object>(), opt =>
{
args.Add("/Q");
}
args.Add("/C");
args.Add(cmd);

return Command.Run("cmd.exe", args, SetCommandOptions);
SetCommandOptions(opt);
opt.StartInfo(info => info.Arguments = arguments);
});
}
else
{
Expand All @@ -373,11 +374,9 @@ public Command Bash(string cmd)
{
Console.WriteLine(cmd);
}
var args = new List<string>();

var escapedArgs = cmd.Replace("\"", "\\\"");
args.Add("-c");
args.Add(escapedArgs);
// Command.Run escapes each argument, so the command goes as typed. (Escaping its quotes here as well left
// bash seeing \" -- a literal quote -- instead of quotes around an argument.)
var args = new List<string> { "-c", cmd };
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return Command.Run("bash.exe", args, SetCommandOptions);
Expand Down
4 changes: 2 additions & 2 deletions src/CShell.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
<RepositoryType>git</RepositoryType>
<PackageTags>scripting dotnet csharp</PackageTags>
<PackageId>CShell</PackageId>
<AssemblyVersion>3.2.0</AssemblyVersion>
<Version>3.2.0</Version>
<AssemblyVersion>3.2.1</AssemblyVersion>
<Version>3.2.1</Version>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
Expand Down
Loading