@@ -15,29 +15,66 @@ defmodule Mix.Tasks.Cmd do
1515
1616 $ mix cmd make
1717
18- By passing the `--cd` flag before the command, you can also force
19- the command to run in a specific directory:
18+ ## Shell expansion
2019
21- $ mix cmd --cd "third-party" make
20+ When you invoke `mix cmd` from the command line, environment variables,
21+ glob patterns, and similar are expanded by the current shell and then
22+ passed to `mix cmd`. For example, if you invoke:
23+
24+ $ mix cmd echo lib/*
25+
26+ Your shell will expand "lib/*" and then pass multiple arguments to
27+ `mix cmd`, which in turn passes them to `echo`. Note that, `mix cmd`
28+ by itself, does not perform any shell expansion. This means that,
29+ if you invoke `mix cmd` programatically, as in:
30+
31+ Mix.Task.run("cmd", ["echo", "lib/*"])
32+
33+ or through a `mix.exs` alias:
34+
35+ alias: "cmd echo lib/*"
36+
37+ It will literally print "lib/*".
38+
39+ This behaviour is the default from Elixir v1.19. If you want
40+ `mix cmd` to behave like a shell, you can pass the `--shell`
41+ option before the command name:
42+
43+ Mix.Task.run("cmd", ["--shell", "echo", "lib/*"])
44+
45+ Keep in mind however that, if `--shell` is given, quoted arguments
46+ are no longer correctly propagated to the underlying command
47+ (as they get expanded before hand).
48+
49+ This task is automatically re-enabled, so it can be called multiple times
50+ with different arguments.
51+
52+ ## Umbrella applications and directories
2253
2354 This task is also useful in umbrella applications to execute a command
2455 on each child app:
2556
2657 $ mix cmd pwd
2758
59+ In such cases, Mix will change the current working directory to the root
60+ of each umbrella application before executing the command.
61+
2862 You can limit which apps the cmd runs in by using `mix do` with the `--app`
2963 option:
3064
3165 $ mix do --app app1 --app app2 cmd pwd
3266
33- The tasks aborts whenever a command exits with a non-zero status.
67+ Note the tasks aborts whenever a command exits with a non-zero status.
3468
35- This task is automatically re-enabled, so it can be called multiple times
36- with different arguments.
69+ Outside of umbrella projects, you can pass the `--cd` flag before the command,
70+ to specify a directory:
71+
72+ $ mix cmd --cd "third-party" make
3773
3874 ## Command line options
3975
4076 * `--cd` *(since v1.10.4)* - the directory to run the command in
77+ * `--shell` *(since v1.19.0)* - perform shell expansion of the arguments
4178
4279 ## Orphan operating system processes
4380
@@ -54,7 +91,8 @@ defmodule Mix.Tasks.Cmd do
5491
5592 @ switches [
5693 app: :keep ,
57- cd: :string
94+ cd: :string ,
95+ shell: :boolean
5896 ]
5997
6098 @ impl true
@@ -78,16 +116,21 @@ defmodule Mix.Tasks.Cmd do
78116 path = hd ( args )
79117 rest = tl ( args )
80118
81- path =
82- if String . contains? ( path , [ "/" , "\\ " ] ) and Path . type ( path ) != :absolute do
83- Path . expand ( path , Keyword . get ( opts , :cd , "." ) )
84- else
85- path
119+ arg =
120+ cond do
121+ Keyword . get ( opts , :shell , false ) ->
122+ Enum . join ( [ path | rest ] , " " )
123+
124+ String . contains? ( path , [ "/" , "\\ " ] ) and Path . type ( path ) != :absolute ->
125+ { Path . expand ( path , Keyword . get ( opts , :cd , "." ) ) , rest }
126+
127+ true ->
128+ { path , rest }
86129 end
87130
88131 cmd_opts = Keyword . take ( opts , [ :cd ] )
89132
90- case Mix . shell ( ) . cmd ( { path , rest } , cmd_opts ) do
133+ case Mix . shell ( ) . cmd ( arg , cmd_opts ) do
91134 0 -> :ok
92135 status -> exit ( status )
93136 end
0 commit comments