Skip to content

Unexpected handling of combination of backslashes and spaces #14

Description

@tkeith

I might be misunderstanding the intended usage of shell-quote, but it seems like strings containing both backslashes and spaces are not handled properly:

Let's consider the string foo \ bar. quote(["foo \\ bar"]) returns 'foo \\ bar', and in bash, echo 'foo \\ bar' prints foo \\ bar.

This failure case does not occur with the same string without spaces (foo\bar): quote(["foo\\bar"]) returns foo\\bar, and in bash, echo foo\\bar prints foo\bar.

Here's a quick typescript file to reproduce with a few test cases:

import { quote } from "shell-quote";
import { spawn } from "child_process";

function spawnCommandWithArgs(
  command: string,
  args: string[] = [],
): Promise<string> {
  return new Promise((resolve, reject) => {
    const childProcess = spawn(command, args, {
      stdio: "pipe", // Pipe stdout and stderr to parent
    });

    let stdout = "";
    let stderr = "";

    // Collect data from stdout
    childProcess.stdout.on("data", (data: Buffer) => {
      stdout += data.toString();
    });

    // Collect data from stderr
    childProcess.stderr.on("data", (data: Buffer) => {
      stderr += data.toString();
    });

    // Handle command completion
    childProcess.on("close", (code) => {
      if (code === 0) {
        resolve(stdout);
      } else {
        reject(new Error(stderr || `Command failed with exit code ${code}`));
      }
    });

    // Handle errors starting the process
    childProcess.on("error", (err) => {
      reject(err);
    });
  });
}

async function main() {
  const testStrings = [
    "foo \\ bar",
    "foo\\bar",
    "foo \\\\ bar",
    "foo\\\\bar",
    "foo\nbar",
    "foo\\\nbar",
  ];

  console.log();

  for (const testString of testStrings) {
    const quotedWithShellQuote = quote([testString]);
    const echoWithShellQuote = await spawnCommandWithArgs("bash", [
      "-c",
      `echo -n ${quotedWithShellQuote}`,
    ]);

    if (echoWithShellQuote === testString) {
      console.log("PASSED:");
    } else {
      console.log("FAILED:");
    }

    console.log("*** begin test string");
    console.log(testString);
    console.log("*** end test string");

    if (echoWithShellQuote !== testString) {
      console.log("*** begin quoted with shell-quote");
      console.log(quotedWithShellQuote);
      console.log("*** end quoted with shell-quote");
      console.log("*** begin echoed output");
      console.log(echoWithShellQuote);
      console.log("*** end echoed output");
    }

    console.log();
  }
}

void main()
  .then(() => {
    process.exit(0);
  })
  .catch((e) => {
    console.error(e);
    process.exit(1);
  });

The output of running that file is:

FAILED:
*** begin test string
foo \ bar
*** end test string
*** begin quoted with shell-quote
'foo \\ bar'
*** end quoted with shell-quote
*** begin echoed output
foo \\ bar
*** end echoed output

PASSED:
*** begin test string
foo\bar
*** end test string

FAILED:
*** begin test string
foo \\ bar
*** end test string
*** begin quoted with shell-quote
'foo \\\\ bar'
*** end quoted with shell-quote
*** begin echoed output
foo \\\\ bar
*** end echoed output

PASSED:
*** begin test string
foo\\bar
*** end test string

PASSED:
*** begin test string
foo
bar
*** end test string

FAILED:
*** begin test string
foo\
bar
*** end test string
*** begin quoted with shell-quote
'foo\\
bar'
*** end quoted with shell-quote
*** begin echoed output
foo\\
bar
*** end echoed output

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions