runtime: Problem parsing on single quotes in argument for Process.Start (dotnet Core, Linux console)

(2019-07-21: rewrote this issues to simplify)

I’m executing console programs in Linux using

I’m executing programs on Linux with Process.Start. Sometimes I need to provide single quotes in the arguments but I’ve not been able to succeed no matter how I escape the character. A simplified example:

using System;
using System.Diagnostics;

namespace Execute_test
{
    class Program
    {
        static void Main(string[] args)
        {
            Process proc = new System.Diagnostics.Process();
            ProcessStartInfo pi = new ProcessStartInfo("ls");
            pi.Arguments = "-l '/tmp/'";
            proc.StartInfo = pi;
            proc.Start();
            do { System.Threading.Thread.Sleep(50); } while (proc.HasExited == false);
            Environment.Exit(0);
        }
    }
}

I do know I don’t have to surround /tmp/ in single quote, but it’s just to make a simple example (please don’t suggest alternatives, that’s not the issue!) The err.out from this example is ls: cannot access "'/tmp/'": No such file or directory

I’ve tried to run this code in .net Core 2.2 and the newest .net Core 3.0.100-preview6-012264 in both C# and VB.

I also tried to use the ProcessStartInfo.ArgumentList it doesn’t parse single quotes any better.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 29 (10 by maintainers)

Most upvoted comments

OMG!!! ProcessStartInfo pi = new ProcessStartInfo("ls"){ ArgumentList = {"'","/tmp/","'"} }; Pure beauty, thaaaaanks

@jnm2 I’m confused, shouldn’t it be

ArgumentList = { "-W", "-f= ${db:Status-Status} ", "mariadb*" }

and

ArgumentList = { "qemu-agent-command", "SRV01", "{\"execute\":\"guest-ping\"}" }?

If you execute dpkg-query -W -f=' ${db:Status-Status} ' mariadb* in bash, the ' are not passed to the executable, as you can easily test by putting printf '%s\n' before the commands. Running printf '%s\n' dpkg-query -W -f=' ${db:Status-Status} ' mariadb* in bash gives

dpkg-query
-W
-f= ${db:Status-Status}
mariadb*

as output, so ' is not contained in the arguments, if dpkg-query -W -f=' ${db:Status-Status} ' mariadb* is executed in bash.

@MrM40 Why aren’t you doing ArgumentList = { "-W", "-f=' ${db:Status-Status} '", "mariadb*" }?

And ArgumentList = { "qemu-agent-command", "SRV01", "'{\"execute\":\"guest-ping\"}'" }?

    class Program
    {
        static void Main(string[] args)
        {
            Process proc = new System.Diagnostics.Process();
            ProcessStartInfo pi = new ProcessStartInfo("sh"){
                ArgumentList = {
                    "-c",
                    "ls '/tmp/'" 
                }
            };
            proc.StartInfo = pi;
            proc.Start();
            do { System.Threading.Thread.Sleep(50); } while (proc.HasExited == false);
            Environment.Exit(0);
        }
    }

works fine for me. If you want ' to be considered a quote, you need some executable, that considers it a quote - for example sh or bash.

You want to start the application from .NET Core which you start on the command line as:

$ /bin/bash -c 'ls -l /etc/ > /tmp/list.txt'

Let’s first figure out how many arguments are involved when starting the application. We’ll use strace and trace for the execve call:

$ strace -e execve -qq /bin/bash -c 'ls -l /etc/ > /tmp/list.txt'
execve("/bin/bash", ["/bin/bash", "-c", "ls -l /etc/ > /tmp/list.txt"], 0x7ffe1e57c978 /* 71 vars */) = 0

As we can see in the output, two arguments are passed to bash: -c and ls -l /etc/ > /tmp/list.txt.

We need a way of passing the second argument so it gets treated as a whole (that is, not split at the spaces).

For bash, there are a number of options. One is to use single quotes, like /bin/bash -c 'ls -l /etc/ > /tmp/list.txt'.

For ProcessStartInfo.Arguments, we need to use double quotes.

static void Main(string[] args)
{
    Process.Start(
        new ProcessStartInfo
        {
            FileName = "/bin/bash",
            Arguments = "-c \"ls -l /etc/ > /tmp/list.txt\""
        }
    ).WaitForExit();

    if (File.Exists("/tmp/list.txt"))
    {
        System.Console.WriteLine("The file exists!");
    }
}

prints out:

The file exists!

ProcessStartInfo pi = new ProcessStartInfo("ls"){ ArgumentList = {"/tmp/"} }; is enough - you don’t need a ' in that case.

If you execute ls -l '/tmp/' '/path/with space/' in bash or sh, then ls never sees any ' - they are removed by the calling shell. ls just gets an array of

ls
-l
/tmp/
/path/with space/

No no no! This is equivalent of executing ls "'" "/tmp/" "'" in bash. It works, but does not do what you want. It lists the files of a folder named ' (twice) and lists the contents of /tmp/.

Please delete (or edit) your comment https://github.com/dotnet/corefx/issues/23592#issuecomment-514241549 as it could heavily confuse newcomers.

.NET Core added an ArgumentsList on ProcessStartInfo. You don’t need to deal with escaping if you are using that. You can Add the ‘-c’ and ‘ls -l /etc/ > /tmp/list.txt’ to the ArgumentsList.