Write command-line apps
- Running an app with the standalone Dart VM
- Overview of the dcat app code
- Parsing command-line arguments
- Reading and writing with stdin, stdout, and stderr
- Getting info about a file
- Reading a file
- Writing to a file
- Getting environment information
- Setting exit codes
- Summary
- What next?
This tutorial teaches you how to build command-line apps and shows you a few small command-line applications. These programs use resources that most command-line applications need, including the standard output, error, and input streams, command-line arguments, files and directories, and more.
Running an app with the standalone Dart VM
#
To run a command-line app in the Dart VM, use
dart run
.
The
dart
commands are included with the
Dart SDK.
Let's run a small program.
-
Create a file called
hello_world.dart
that contains this code:dartvoid main() { print('Hello, World!'); }
-
In the directory that contains the file you just created, run the program:
console$ dart run hello_world.dart Hello, World!
The Dart tool supports many commands and options.
Use
dart --help
to see commonly used commands and options.
Use
dart --verbose
to see all options.
Overview of the dcat app code
#
This tutorial covers the details of a small sample app called
dcat
, which
displays the contents of any files listed on the command line.
This app uses various classes, functions, and properties
available to command-line apps.
Continue on in the tutorial to learn about each part of the app
and the various APIs used.
import 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
const lineNumber = 'line-number';
void main(List<String> arguments) {
exitCode = 0; // Presume success
final parser = ArgParser()..addFlag(lineNumber, negatable: false, abbr: 'n');
ArgResults argResults = parser.parse(arguments);
final paths = argResults.rest;
dcat(paths, showLineNumbers: argResults[lineNumber] as bool);
}
Future<void> dcat(List<String> paths, {bool showLineNumbers = false}) async {
if (paths.isEmpty) {
// No files provided as arguments. Read from stdin and print each line.
await stdin.pipe(stdout);
} else {
for (final path in paths) {
var lineNumber = 1;
final lines = utf8.decoder
.bind(File(path).openRead())
.transform(const LineSplitter());
try {
await for (final line in lines) {
if (showLineNumbers) {
stdout.write('${lineNumber++} ');
}
stdout.writeln(line);
}
} catch (_) {
await _handleError(path);
}
}
}
}
Future<void> _handleError(String path) async {
if (await FileSystemEntity.isDirectory(path)) {
stderr.writeln('error: $path is a directory');
} else {
exitCode = 2;
}
}
Getting dependencies
#You might notice that dcat depends on a package named args. To get the args package, use the pub package manager.
A real app has tests, license files, dependency files, examples, and so on.
For the first app though, we can easily create only what is necessary
with the
dart create
command.
-
Inside a directory, create the dcat app with the dart tool.
console$ dart create dcat
-
Change to the created directory.
console$ cd dcat
-
Inside the
dcat
directory, usedart pub add
to add theargs
package as a dependency. This addsargs
to the list of your dependencies found in thepubspec.yaml
file.console$ dart pub add args
Open the
bin/dcat.dart
file and copy the preceding code into it.
Running dcat
#
Once you have your app's dependencies,
you can run the app from the command line over any text file,
like
pubspec.yaml
:
$ dart run bin/dcat.dart -n pubspec.yaml
1 name: dcat
2 description: A sample command-line application.
3 version: 1.0.0
4 # repository: https://github.com/my_org/my_repo
5
6 environment:
7 sdk: ^3.9.0
8
9 # Add regular dependencies here.
10 dependencies:
11 args: ^2.7.0
12 # path: ^1.8.0
13
14 dev_dependencies:
15 lints: ^6.0.0
16 test: ^1.25.0
This command displays each line of the specified file.
Because you specified the
-n
option,
a line number is displayed before each line.
Parsing command-line arguments
#The args package provides parser support for transforming command-line arguments into a set of options, flags, and additional values. Import the package's args library as follows:
import 'package:args/args.dart';
The args
library contains these classes, among others:
Class | Description |
---|---|
ArgParser | A command-line argument parser. |
ArgResults | The result of parsing command-line arguments using ArgParser . |
The following code in the
dcat
app uses these classes to
parse and store the specified command-line arguments:
void main(List<String> arguments) {
exitCode = 0; // Presume success
final parser = ArgParser()..addFlag(lineNumber, negatable: false, abbr: 'n');
ArgResults argResults = parser.parse(arguments);
final paths = argResults.rest;
dcat(paths, showLineNumbers: argResults[lineNumber] as bool);
}
The Dart runtime passes command-line arguments to
the app's
main
function as a list of strings.
The
ArgParser
is configured to parse the
-n
option.
Then, the result of parsing command-line arguments is stored in
argResults
.
The following diagram shows how the
dcat
command line used above
is parsed into an
ArgResults
object.
You can access flags and options by name,
treating an
ArgResults
like a
Map
.
You can access other values using the
rest
property.
The
API reference
for the
args
library provides detailed information to help you use
the
ArgParser
and
ArgResults
classes.
Reading and writing with stdin, stdout, and stderr
#
Like other languages,
Dart has standard output, standard error, and standard input streams.
The standard I/O streams are defined at the top level of the
dart:io
library:
Import the dart:io
library as follows:
import 'dart:io';
stdout
#
The following code from the
dcat
app
writes the line numbers to
stdout
(if the
-n
option is specified)
followed by the contents of the line from the file.
if (showLineNumbers) {
stdout.write('${lineNumber++} ');
}
stdout.writeln(line);
The
write()
and
writeln()
methods take an object of any type,
convert it to a string, and print it.
The
writeln()
method also prints a newline character.
The
dcat
app uses the
write()
method to print the line number so
the line number and text appear on the same line.
You can also use the
writeAll()
method to print a list of objects,
or use
addStream()
to asynchronously print all the elements from a stream.
stdout
provides more functionality than the
print()
function.
For example, you can display the contents of a stream with
stdout
.
However, you must use
print()
instead of
stdout
for apps that run on the web.
stderr
#
Use
stderr
to write error messages to the console.
The standard error stream has the same methods as
stdout
,
and you use it in the same way.
Although both
stdout
and
stderr
print to the console,
their output is separate and
can be redirected or piped in the command line
or programmatically to different destinations.
The following code from the
dcat
app prints an error message if
the user tries to output the lines of a directory instead of a file.
if (await FileSystemEntity.isDirectory(path)) {
stderr.writeln('error: $path is a directory');
} else {
exitCode = 2;
}
stdin
#The standard input stream typically reads data synchronously from the keyboard, although it can read asynchronously and get input piped in from the standard output of another program.
Here's a small program that reads a single line from stdin
:
import 'dart:io';
void main() {
stdout.writeln('Type something');
final input = stdin.readLineSync();
stdout.writeln('You typed: $input');
}
The
readLineSync()
method reads text from the standard input stream,
blocking until the user types in text and presses return.
This little program prints out the typed text.
In the
dcat
app,
if the user does not provide a filename on the command line,
the program instead reads from stdin using the
pipe()
method.
Because
pipe()
is asynchronous
(returning a
Future
, even though this code doesn't use that return value),
the code that calls it uses
await
.
await stdin.pipe(stdout);
In this case, the user types in lines of text, and the app copies them to stdout. The user signals the end of input by pressing Control+D (or Control+Z on Windows).
$ dart run bin/dcat.dart
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
Getting info about a file
#
The
FileSystemEntity
class in the
dart:io
library provides properties and static methods
that help you inspect and manipulate the file system.
For example, if you have a path, you can
determine whether the path is a file, a directory, a link, or not found by
using the
type()
method from the
FileSystemEntity
class.
Because the
type()
method accesses the file system,
it performs the check asynchronously.
The following code from the
dcat
app uses
FileSystemEntity
to determine if the path provided on the command line is a directory.
The returned
Future
completes with a boolean that
indicates if the path is a directory or not.
Because the check is asynchronous, the
code calls
isDirectory()
using
await
.
if (await FileSystemEntity.isDirectory(path)) {
stderr.writeln('error: $path is a directory');
} else {
exitCode = 2;
}
Other interesting methods in the
FileSystemEntity
class
include
isFile()
,
exists()
,
stat()
,
delete()
, and
rename()
,
all of which also use a
Future
to return a value.
FileSystemEntity
is the superclass for the
File
,
Directory
, and
Link
classes.
Reading a file
#
The
dcat
apps opens each file listed on the command line
with the
openRead()
method, which returns a
Stream
.
The
await for
block waits for the file to be read and decoded asynchronously.
When the data becomes available on the stream,
the app prints it to stdout.
for (final path in paths) {
var lineNumber = 1;
final lines = utf8.decoder
.bind(File(path).openRead())
.transform(const LineSplitter());
try {
await for (final line in lines) {
if (showLineNumbers) {
stdout.write('${lineNumber++} ');
}
stdout.writeln(line);
}
} catch (_) {
await _handleError(path);
}
}
The following highlights the rest of the code, which uses two decoders that
transform the data before making it available in the
await for
block.
The UTF8 decoder converts the data into Dart strings.
LineSplitter
splits the data at newlines.
for (final path in paths) {
var lineNumber = 1;
final lines = utf8.decoder
.bind(File(path).openRead())
.transform(const LineSplitter());
try {
await for (final line in lines) {
if (showLineNumbers) {
stdout.write('${lineNumber++} ');
}
stdout.writeln(line);
}
} catch (_) {
await _handleError(path);
}
}
The
dart:convert
library provides these and other data converters,
including one for JSON.
To use these converters you need to import the
dart:convert
library:
import 'dart:convert';
Writing to a file
#
The easiest way to write text to a file is to create a
File
object and use the
writeAsString()
method:
final quotes = File('quotes.txt');
const stronger = 'That which does not kill us makes us stronger. -Nietzsche';
await quotes.writeAsString(stronger, mode: FileMode.append);
The
writeAsString()
method writes the data asynchronously.
It opens the file before writing and closes the file when done.
To append data to an existing file, you can use the optional
named parameter
mode
and set its value to
FileMode.append
.
Otherwise, the mode defaults to
FileMode.write
and
the previous contents of the file, if any, are overwritten.
If you want to write more data, you can open the file for writing.
The
openWrite()
method returns an
IOSink
,
which has the same type as stdin and stderr.
When using the
IOSink
returned from
openWrite()
,
you can continue to write to the file until done,
at which time, you must manually close the file.
The
close()
method is asynchronous and returns a
Future
.
final quotes = File('quotes.txt').openWrite(mode: FileMode.append);
quotes.write("Don't cry because it's over, ");
quotes.writeln('smile because it happened. -Dr. Seuss');
await quotes.close();
Getting environment information
#
Use the
Platform
class
to get information about the machine and operating system
that your app is running on.
The static
Platform.environment
property
provides a copy of the environment variables in an immutable map.
If you need a mutable map (modifiable copy) you
can use
Map.of(Platform.environment)
.
final envVarMap = Platform.environment;
print('PWD = ${envVarMap['PWD']}');
print('LOGNAME = ${envVarMap['LOGNAME']}');
print('PATH = ${envVarMap['PATH']}');
Platform
provides other useful properties that give
information about the machine, OS, and currently
running app. For example:
Setting exit codes
#
The
dart:io
library defines a top-level property,
exitCode
, that you can change to set the exit code for
the current invocation of the Dart VM.
An exit code is a number passed from
a Dart app to the parent process
to indicate the success, failure, or other state of
the execution of the app.
The
dcat
app sets the exit code
in the
_handleError()
function to indicate that an error
occurred during execution.
Future<void> _handleError(String path) async {
if (await FileSystemEntity.isDirectory(path)) {
stderr.writeln('error: $path is a directory');
} else {
exitCode = 2;
}
}
An exit code of 2
indicates that the app encountered an error.
An alternative to using
exitCode
is to use the top-level
exit()
function,
which sets the exit code and exits the app immediately.
For example, the
_handleError()
function could call
exit(2)
instead of setting
exitCode
to 2,
but
exit()
would quit the program and
it might not process all of the files specified by the running command.
Although you can use any number for an exit code, by convention, the codes in the table below have the following meanings:
Code | Meaning |
---|---|
0 | Success |
1 | Warnings |
2 | Errors |
Summary
#
This tutorial described some basic APIs
found in the following classes from the
dart:io
library:
API | Description |
---|---|
IOSink |
Helper class for objects that consume data from streams |
File |
Represents a file on the native file system |
Directory |
Represents a directory on the native file system |
FileSystemEntity
|
Superclass for File and Directory |
Platform |
Provides information about the machine and operating system |
stdout |
The standard output stream |
stderr |
The standard error stream |
stdin |
The standard input stream |
exitCode |
Access and set the exit code |
exit() |
Sets the exit code and quits |
In addition, this tutorial covered two classes from
package:args
that help with parsing and using command-line arguments:
ArgParser
and
ArgResults
.
For more classes, functions, and properties,
consult the API docs for
dart:io
,
dart:convert
,
and
package:args
.
For another example of a command line app,
check out the
command_line
sample.
What next?
#If you're interested in server-side programming, check out the next tutorial.
Unless stated otherwise, the documentation on this site reflects Dart 3.9.2. Page last updated on 2025-8-13. View source or report an issue.