@@ -984,23 +984,38 @@ defmodule System do
984
984
hello
985
985
{%IO.Stream{}, 0}
986
986
987
+ If you want to read lines:
988
+
989
+ iex> System.cmd("echo", ["hello\nworld"], into: [], lines: 1024)
990
+ {["hello", "world"], 0}
991
+
987
992
## Options
988
993
989
994
* `:into` - injects the result into the given collectable, defaults to `""`
995
+
996
+ * `:lines` - (since v1.15.0) reads the output by lines instead of in bytes. It expects a
997
+ number of maximum bytes to buffer internally (1024 is a reasonable default).
998
+ The collectable will be called with each finished line (regardless of buffer
999
+ size) and without the EOL character
1000
+
990
1001
* `:cd` - the directory to run the command in
1002
+
991
1003
* `:env` - an enumerable of tuples containing environment key-value as
992
1004
binary. The child process inherits all environment variables from its
993
1005
parent process, the Elixir application, except those overwritten or
994
1006
cleared using this option. Specify a value of `nil` to clear (unset) an
995
1007
environment variable, which is useful for preventing credentials passed
996
- to the application from leaking into child processes.
1008
+ to the application from leaking into child processes
1009
+
997
1010
* `:arg0` - sets the command arg0
1011
+
998
1012
* `:stderr_to_stdout` - redirects stderr to stdout when `true`
1013
+
999
1014
* `:parallelism` - when `true`, the VM will schedule port tasks to improve
1000
1015
parallelism in the system. If set to `false`, the VM will try to perform
1001
1016
commands immediately, improving latency at the expense of parallelism.
1002
1017
The default can be set on system startup by passing the "+spp" argument
1003
- to `--erl`.
1018
+ to `--erl`
1004
1019
1005
1020
## Error reasons
1006
1021
@@ -1054,11 +1069,16 @@ defmodule System do
1054
1069
end
1055
1070
1056
1071
defp do_cmd ( port_init , base_opts , opts ) do
1057
- { into , opts } = cmd_opts ( opts , [ :use_stdio , :exit_status , :binary , :hide ] ++ base_opts , "" )
1072
+ { into , line , opts } =
1073
+ cmd_opts ( opts , [ :use_stdio , :exit_status , :binary , :hide ] ++ base_opts , "" , false )
1074
+
1058
1075
{ initial , fun } = Collectable . into ( into )
1059
1076
1060
1077
try do
1061
- do_port ( Port . open ( port_init , opts ) , initial , fun )
1078
+ case line do
1079
+ true -> do_port_line ( Port . open ( port_init , opts ) , initial , fun , [ ] )
1080
+ false -> do_port_byte ( Port . open ( port_init , opts ) , initial , fun )
1081
+ end
1062
1082
catch
1063
1083
kind , reason ->
1064
1084
fun . ( initial , :halt )
@@ -1068,42 +1088,67 @@ defmodule System do
1068
1088
end
1069
1089
end
1070
1090
1071
- defp do_port ( port , acc , fun ) do
1091
+ defp do_port_byte ( port , acc , fun ) do
1072
1092
receive do
1073
1093
{ ^ port , { :data , data } } ->
1074
- do_port ( port , fun . ( acc , { :cont , data } ) , fun )
1094
+ do_port_byte ( port , fun . ( acc , { :cont , data } ) , fun )
1075
1095
1076
1096
{ ^ port , { :exit_status , status } } ->
1077
1097
{ acc , status }
1078
1098
end
1079
1099
end
1080
1100
1081
- defp cmd_opts ( [ { :into , any } | t ] , opts , _into ) ,
1082
- do: cmd_opts ( t , opts , any )
1101
+ defp do_port_line ( port , acc , fun , buffer ) do
1102
+ receive do
1103
+ { ^ port , { :data , { :noeol , data } } } ->
1104
+ do_port_line ( port , acc , fun , [ data | buffer ] )
1105
+
1106
+ { ^ port , { :data , { :eol , data } } } ->
1107
+ data = [ data | buffer ] |> Enum . reverse ( ) |> IO . iodata_to_binary ( )
1108
+ do_port_line ( port , fun . ( acc , { :cont , data } ) , fun , [ ] )
1109
+
1110
+ { ^ port , { :exit_status , status } } ->
1111
+ # Data may arrive after exit status on line mode
1112
+ receive do
1113
+ { ^ port , { :data , { _ , data } } } ->
1114
+ data = [ data | buffer ] |> Enum . reverse ( ) |> IO . iodata_to_binary ( )
1115
+ { fun . ( acc , { :cont , data } ) , status }
1116
+ after
1117
+ 0 -> { acc , status }
1118
+ end
1119
+ end
1120
+ end
1121
+
1122
+ defp cmd_opts ( [ { :into , any } | t ] , opts , _into , line ) ,
1123
+ do: cmd_opts ( t , opts , any , line )
1124
+
1125
+ defp cmd_opts ( [ { :cd , bin } | t ] , opts , into , line ) when is_binary ( bin ) ,
1126
+ do: cmd_opts ( t , [ { :cd , bin } | opts ] , into , line )
1083
1127
1084
- defp cmd_opts ( [ { :cd , bin } | t ] , opts , into ) when is_binary ( bin ) ,
1085
- do: cmd_opts ( t , [ { :cd , bin } | opts ] , into )
1128
+ defp cmd_opts ( [ { :arg0 , bin } | t ] , opts , into , line ) when is_binary ( bin ) ,
1129
+ do: cmd_opts ( t , [ { :arg0 , bin } | opts ] , into , line )
1086
1130
1087
- defp cmd_opts ( [ { :arg0 , bin } | t ] , opts , into ) when is_binary ( bin ) ,
1088
- do: cmd_opts ( t , [ { :arg0 , bin } | opts ] , into )
1131
+ defp cmd_opts ( [ { :stderr_to_stdout , true } | t ] , opts , into , line ) ,
1132
+ do: cmd_opts ( t , [ :stderr_to_stdout | opts ] , into , line )
1089
1133
1090
- defp cmd_opts ( [ { :stderr_to_stdout , true } | t ] , opts , into ) ,
1091
- do: cmd_opts ( t , [ :stderr_to_stdout | opts ] , into )
1134
+ defp cmd_opts ( [ { :stderr_to_stdout , false } | t ] , opts , into , line ) ,
1135
+ do: cmd_opts ( t , opts , into , line )
1092
1136
1093
- defp cmd_opts ( [ { :stderr_to_stdout , false } | t ] , opts , into ) ,
1094
- do: cmd_opts ( t , opts , into )
1137
+ defp cmd_opts ( [ { :parallelism , bool } | t ] , opts , into , line ) when is_boolean ( bool ) ,
1138
+ do: cmd_opts ( t , [ { :parallelism , bool } | opts ] , into , line )
1095
1139
1096
- defp cmd_opts ( [ { :parallelism , bool } | t ] , opts , into ) when is_boolean ( bool ) ,
1097
- do: cmd_opts ( t , [ { :parallelism , bool } | opts ] , into )
1140
+ defp cmd_opts ( [ { :env , enum } | t ] , opts , into , line ) ,
1141
+ do: cmd_opts ( t , [ { :env , validate_env ( enum ) } | opts ] , into , line )
1098
1142
1099
- defp cmd_opts ( [ { :env , enum } | t ] , opts , into ) ,
1100
- do: cmd_opts ( t , [ { :env , validate_env ( enum ) } | opts ] , into )
1143
+ defp cmd_opts ( [ { :lines , max_line_length } | t ] , opts , into , _line )
1144
+ when is_integer ( max_line_length ) and max_line_length > 0 ,
1145
+ do: cmd_opts ( t , [ { :line , max_line_length } | opts ] , into , true )
1101
1146
1102
- defp cmd_opts ( [ { key , val } | _ ] , _opts , _into ) ,
1147
+ defp cmd_opts ( [ { key , val } | _ ] , _opts , _into , _line ) ,
1103
1148
do: raise ( ArgumentError , "invalid option #{ inspect ( key ) } with value #{ inspect ( val ) } " )
1104
1149
1105
- defp cmd_opts ( [ ] , opts , into ) ,
1106
- do: { into , opts }
1150
+ defp cmd_opts ( [ ] , opts , into , line ) ,
1151
+ do: { into , line , opts }
1107
1152
1108
1153
defp validate_env ( enum ) do
1109
1154
Enum . map ( enum , fn
0 commit comments