ZetCode

Dart StdioType

last modified May 28, 2026

The StdioType enum in Dart identifies the type of standard IO streams. The top-level stdioType function from dart:io takes a stream or sink as an argument and returns a StdioType value indicating whether it is a terminal, pipe, file, or other type of stream.

stdioType is part of Dart's dart:io library and is particularly useful when working with process communication or when you need to adjust behavior based on the IO stream type.

Basic Definition

StdioType is an enumeration that identifies different types of standard IO streams. It helps determine how to handle various IO operations.

The class provides constants like TERMINAL, PIPE, FILE, and OTHER to classify standard streams. It's often used with stdin, stdout, and stderr.

Checking stdin Type

This example shows how to check the type of the standard input stream.

main.dart
import 'dart:io';

void main() {
  var stdinType = stdioType(stdin);
  
  if (stdinType == StdioType.terminal) {
    print('Input is from a terminal');
  } else if (stdinType == StdioType.pipe) {
    print('Input is from a pipe');
  } else {
    print('Input is of type: $stdinType');
  }
}

We pass stdin to the top-level stdioType function to determine its type. This helps establish whether the program is receiving input interactively or through a pipe.

$ dart main.dart
Input is from a terminal

$ echo "test" | dart main.dart
Input is from a pipe

Detecting Output Stream Type

This example demonstrates checking the type of standard output streams.

main.dart
import 'dart:io';

void main() {
  checkStreamType(stdout, 'stdout');
  checkStreamType(stderr, 'stderr');
}

void checkStreamType(IOSink stream, String name) {
  var type = stdioType(stream);
  
  print('$name type: $type');
  print('Is terminal: ${type == StdioType.terminal}');
}

We examine both stdout and stderr streams to determine their types. This can be useful for formatting output differently based on destination.

$ dart test.dart 
stdout type: StdioType: terminal
Is terminal: true
stderr type: StdioType: terminal
Is terminal: true

Handling Different Stream Types

This example shows how to adjust behavior based on the stream type.

main.dart
import 'dart:io';

void main() {
  var output = stdout;
  
  switch (stdioType(output)) {
    case StdioType.terminal:
      output.writeln('Writing to terminal - adding colors');
      break;
    case StdioType.pipe:
      output.writeln('Writing to pipe - plain output');
      break;
    case StdioType.file:
      output.writeln('Writing to file - no terminal features');
      break;
    default:
      output.writeln('Writing to unknown stream type');
  }
}

We use a switch statement to handle different output stream types differently. This pattern is common for CLI tools that adapt to their environment.

$ dart main.dart
Writing to terminal - adding colors

$ dart main.dart | cat
Writing to pipe - plain output

Checking File Descriptors

This example demonstrates checking StdioType for arbitrary file descriptors.

main.dart
import 'dart:io';

void main() async {
  var file = File('test.txt');
  var sink = file.openWrite();
  
  print('File stream type: ${stdioType(sink)}');
  
  await sink.close();
  
  var socket = await Socket.connect('google.com', 80);
  print('Socket stream type: ${stdioType(socket)}');
  socket.destroy();
}

We pass different IO objects to the top-level stdioType function. For objects that are not standard streams (such as a file sink or socket), it returns StdioType.other.

$ dart main.dart
File stream type: StdioType.FILE
Socket stream type: StdioType.OTHER

Process Communication with StdioType

This example shows using StdioType in process communication scenarios.

main.dart
import 'dart:convert';
import 'dart:io';

void main() async {
  var process = await Process.start('ls', ['-l']);

  print('Process stdin type:  ${stdioType(process.stdin)}');
  print('Process stdout type: ${stdioType(process.stdout)}');
  print('Process stderr type: ${stdioType(process.stderr)}');

  print('process output lines:');
  process.stdout
      .transform(SystemEncoding().decoder)
      .transform(const LineSplitter())
      .listen(print);

  await process.exitCode;
}

When spawning processes, StdioType helps determine how to handle the process streams. This is useful for building process management tools.

Inspecting Subprocess Streams

The next example shows how StdioType can be used in a more practical setting. Instead of inspecting only the current process, we launch external commands, capture their output, and report the stream types associated with each subprocess. The helper function runAndInspect encapsulates this workflow so the program can start a command, print the StdioType values for its streams, drain the output safely, and return the collected data for further use.

main.dart
import 'dart:convert';
import 'dart:io';

/// Runs a subprocess and reports its StdioType for stdin/stdout/stderr,
/// then collects and returns all stdout lines.
Future<List<String>> runAndInspect(String cmd, List<String> args) async {
  final process = await Process.start(cmd, args);

  final label = '[$cmd ${args.join(" ")}]';
  print('$label  stdin : ${stdioType(process.stdin)}');
  print('$label  stdout: ${stdioType(process.stdout)}');
  print('$label  stderr: ${stdioType(process.stderr)}');

  // Capture stdout lines while draining stderr so the process never blocks.
  final lines = <String>[];
  final decoder = SystemEncoding().decoder;

  final stdoutDone = process.stdout
      .transform(decoder)
      .transform(const LineSplitter())
      .forEach((line) => lines.add(line));

  final stderrDone = process.stderr
      .transform(decoder)
      .transform(const LineSplitter())
      .forEach((line) => stderr.writeln('[stderr] $line'));

  await Future.wait([stdoutDone, stderrDone]);
  await process.exitCode;
  return lines;
}

void main(List<String> argv) async {
  final dir = argv.isNotEmpty ? argv[0] : '.';

  // ── 1. List files ────────────────────────────────────────────────────────
  print('\n=== ls: list files in "$dir" ===');
  final entries = await runAndInspect('ls', ['-1', dir]);
  print('Found ${entries.length} entries\n');

  // ── 2. Count lines in each file ───────────────────────────────────────────
  final files = entries
      .map((e) => '$dir/$e')
      .where((p) => File(p).existsSync())
      .toList();

  if (files.isNotEmpty) {
    print('=== wc: count lines ===');
    final counts = await runAndInspect('wc', ['-l', ...files]);
    counts.forEach(print);
    print('');
  }

  // ── 3. Detect file types ──────────────────────────────────────────────────
  if (files.isNotEmpty) {
    print('=== file: detect MIME types ===');
    final types = await runAndInspect('file', ['--mime-type', ...files]);
    types.forEach(print);
    print('');
  }

  // ── 4. Compare against our own stdio ─────────────────────────────────────
  print('=== this process own stdio ===');
  print('stdin : ${stdioType(stdin)}');
  print('stdout: ${stdioType(stdout)}');
  print('stderr: ${stdioType(stderr)}');
}

This example demonstrates how stream-type inspection fits naturally into larger process-handling tasks. By checking the StdioType of each stream, the program can adjust its behavior depending on whether it is connected to a terminal or a pipe. The structure also highlights a clean way to manage subprocess output without blocking, while keeping the logic reusable for any command the program needs to run.

                ┌──────────────────────────────┐
                │        Your Terminal         │
                │  (keyboard + screen + TTY)   │
                └──────────────┬───────────────┘
                               │
                               │  real terminal
                               │
                    ┌──────────▼─────────-─┐
                    │   Dart Program       │
                    │  (first.dart)        │
                    ├──────────────────────┤
                    │ stdin  : terminal    │
                    │ stdout : terminal    │
                    │ stderr : terminal    │
                    └───────┬─────┬────────┘
                            │     │
                            │     │ creates pipes
                            │     │
        ┌───────────────────┘     └─────────-──────────┐
        │                                              │
┌───────▼───────-─┐                           ┌────────▼─────-──┐
│   Child Proc    │                           │   Child Proc    │
│      ls         │                           │      wc         │
├─────────────────┤                           ├─────────────────┤
│ stdin  : other  │                           │ stdin  : other  │
│ stdout : pipe   │                           │ stdout : pipe   │
│ stderr : pipe   │                           │ stderr : pipe   │
└───────┬─────────┘                           └────────┬────────┘
        │                                              │
        │  pipes (not a terminal)                      │
        │                                              │
        └──────────────────────────────────────────────┘

This diagram shows how the program's own standard streams are connected to the terminal, while the child processes' streams are connected via pipes.

Best Practices

Source

Dart StdioType Documentation

This tutorial covered Dart's StdioType class with practical examples showing how to identify and handle different standard IO stream types in Dart.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.