2

If you run this command in a Linux terminal:

mkdir -p ./dist/{articles,scripts,stylesheets}

It'll create the following folder tree (in the current directory):

dist
|- articles
|- scripts
|- stylesheets

The problem occurs when I try to do the same using the shelljs npm package.

For example, calling the following function:

shell.mkdir("-p", "./dist/{articles,scripts,stylesheets}");

Results in the following file tree being created:

dist
|- {articles,scripts,stylesheets}

In other words, it's a folder called dist that contains a subfolder called {articles,scripts,stylesheets}.

I've tried escaping the curly braces, like this:

shell.mkdir("-p", "./dist/\{articles,scripts,stylesheets\}");

It didn't work, so I doubled down and escaped the backslash:

shell.mkdir("-p", "./dist/\\{articles,scripts,stylesheets\\}");

That didn't work either, so I doubled down again and added an escaped backslash before the escaped backslash:

shell.mkdir("-p", "./dist/\\\\{articles,scripts,stylesheets\\\\}");

Which didn't work, but it did create a folder with a different name:

\\{articles,scripts,stylesheets\\}

How can I fix this problem?

Malekai
  • 4,765
  • 5
  • 25
  • 60

2 Answers2

4

The correct way is to rewrite brace expansion using loops or similar:

const shell = require('shelljs')
for(var dir of ["articles", "scripts", "stylesheets"]) {
  shelljs.mkdir("-p", "./dist/" + dir)
}

This is fast, robust and portable.

Equivalently, you can use a third party library that expands them for you:

const shell = require('shelljs')
const braces = require('braces')
shell.mkdir("-p", braces("./dist/{articles,scripts,stylesheets}", {expand: true}))

The literal way is to explicitly invoke Bash, since brace expansion is a bash feature:

shelljs.exec("bash -c 'mkdir -p ./dist/{articles,scripts,stylesheets}'")

This is slow, fragile and non-portable because it requires two invocations of two Unix shells and two corresponding levels of escaping.

The point of shelljs is to replace such code with pure JS implementations so that it requires zero invocations of zero shells, so this entirely defeats the purpose of using it in the first place.

that other guy
  • 116,971
  • 11
  • 170
  • 194
0

shelljs mkdir() command takes as parameter a list or an array of directory names. It will not attempt to execute any command or sequence builder utility provided by bash, as we can see in source code. So there is no point to try to escape the braces.

Instead you could send the raw command with exec():

shell.exec("bash -c 'mkdir -p ./dist/{articles,scripts,stylesheets}'")
TGrif
  • 5,725
  • 9
  • 31
  • 52
  • Thanks for taking the time to answer, but this solution doesn't work, it's still creating a sub folder called `{articles,scripts,stylesheets}`. – Malekai May 17 '19 at 13:02
  • You're seeing different results because it relies on undefined behavior. `shelljs` invokes `sh`, so this snippet will work on macOS and CentOS where `sh` is `bash`, while it fails on Debian/Ubuntu and Alpine where `sh` is `dash` and BusyBox `ash` respectively. – that other guy May 17 '19 at 22:32