Allows to run script from Mac/Windows/Linux in a portable way. Empty lines are added for readibility
var shell = Shell();
await shell.run('''
# Display some text
echo Hello
# Display dart version
dart --version
# Display pub version
pub --version
''');
the command will be looked in the system paths (PATH
variable). See section later in this this document about
adding system paths.
A script is composed of 1 or multiple lines. Each line becomes a command:
- Each line is trimmed.
- A line starting with
#
will be ignored.//
and///
comments are also supported - A line ending with
^
(a space and the^
character) or\\
(a space and one backslash) continue on the next line. - Each command must evaluate to one executable (i.e. no loop, pipe, redirection, bash/powershell specific features).
- Each first word of the line is the executable whose path is resolved using the
which
command.
If you have spaces in one argument, it must be escaped using double quotes or the shellArgument
method:
import 'package:process_run/shell_run.dart';
await run('echo "Hello world"');
await run('echo ${shellArgument('Hello world')}');
runSync
and runExecutableArgumentsSync
are available since 0.14.1 in the global space and in the Shell
class.
They are synchronous version of run
and runExecutableArguments
respectively with some limitations.
The synchronous mode is useful for testing. It is not recommended for production use as it is a synchronous call and will block until the child process terminates.
var shell = Shell();
// This is a synchronous call and will block until the child process terminates.
var results = shell.runSync('echo "Hello world"');
var result = results.first;
print('output: "${result.outText.trim()}" exitCode: ${result.exitCode}');
// should display: output: "Hello world" exitCode: 0
Warning:
- You cannot feed any stdin to the child process.
- You cannot kill a synchronous child process.
- Available since 0.14.1
- Recommended for testing only, it is a synchronous call and will block until the child process terminates.
By default, run
will throw an error if the exitCode
is not 0. You can prevent that
with the option throwOnError
which is true by default:
void main(List<String> arguments) async {
// Prevent error to be thrown if exitCode is not 0
var shell = Shell(throwOnError: false);
// This won't throw
await shell.run('dir dummy_folder');
shell = Shell();
// This throws an error!
await shell.run('dir dummy_folder');
}
If somehow you cannot modify the system path, it will look for any path (last) defined in
~/.config/tekartik/process_run/env.yaml
on Mac/Linux or %APPDATA%\tekartik\process_run\env.yaml
on Windows.
See User configuration file documentation.
$ pub global active process_run $ alias ds='dart pub global run process_run:shell'
ShellLinesController
allows listening line from a command line script.
var controller = ShellLinesController();
var shell = Shell(stdout: controller.sink, verbose: false);
controller.stream.listen((event) {
// Handle output
// ...
// If needed kill the shell
shell.kill();
});
try {
await shell.run('dart echo.dart some_text');
} on ShellException catch (_) {
// We might get a shell exception
}
You can run your dart program using sudo to run all you child scripts as a super user.
Running a shell script with sudo from inside a dart script ran in non super user mode
is a little bit trickier as it requires user interaction. One solution (tried on Ubuntu) is to use
sudo --stdin
to specify reading the password from the stdin.
You can then run script using the following:
await shell.run('sudo --stdin lsof -i:22');
A shared stdin object could be used to redirect dart program input to shell objects.
Here is a more complex example:
import 'package:process_run/shell.dart';
/// Only works on linux, list the process listening on port 22
void main(List<String> arguments) async {
/// We have use a shared stdin if we want to reuse it.
var stdin = sharedStdIn;
/// Use sudo --stdin to read the password from stdin
/// Use an alias for simplicity (only need to refer to sudo instead of sudo --stdin
var env = ShellEnvironment()..aliases['sudo'] = 'sudo --stdin';
var shell = Shell(
stdin: sharedStdIn,
// lsof return exitCode 1 if not found
environment: env,
throwOnError: false);
await shell.run('sudo lsof -i:22');
// second time should not ask for password
await shell.run('sudo lsof -i:80');
/// Stop shared stdin
await stdin.terminate();
}
If you have the password in a variable and not access to stdin (for example in some flutter scenario), you can do something like:
import 'dart:io';
import 'package:http/http.dart';
import 'package:process_run/shell.dart';
import 'package:tekartik_common_utils/common_utils_import.dart';
void main(List<String> arguments) async {
// Assuming you have the password in `pwd` variable
// Use sudo --stdin to read the password from stdin
// Use an alias for simplicity (only need to refer to sudo instead of sudo --stdin
var env = ShellEnvironment()..aliases['sudo'] = 'sudo --stdin';
// Create a fake stdin stream from the password variable
var stdin =
ByteStream.fromBytes(systemEncoding.encode(pwd)).asBroadcastStream();
// Execute!
var shell = Shell(stdin: stdin, environment: env);
// Should not ask for password
await shell.run('sudo lsof -i:22');
await shell.run('sudo lsof -i:80');
}
Turn off Sandboxing by removing it from the Signing & Capabilities tab:
Then run your commands via osascript
like so:
await shell.run('''
osascript -e 'do shell script "[YOUR_SHELL_COMMAND_GOES_HERE]" with administrator privileges'
''')
That will prompt for the user to type his password and then will run the script.
You can kill a shell using the kill()
method. It will kill the shell and all its spawn children.
By default its uses ProcessSignal.sigterm
signal. You can specify a different signal using the signal
parameter.
A special trick is made when ProcessSignal.sigkill
used as it will force killing all children processes on Windows (taskkill), MacOS and Linux (pkill).
As I was experiencing with dhttpd
, killing the process was not really killing the server so using sigkill
(and so taskkill and pkill) was properly
killing the server.