Saltar a contenido

7.A Consola: entrada y salida estándar

Cuando un programa se ejecuta en una terminal, el sistema le da tres canales de texto, llamados flujos estándar:

Flujo Nombre Para qué sirve Por defecto En C#
stdin Entrada estándar Lo que el programa recibe El teclado Console.In · Console.ReadLine()
stdout Salida estándar Los resultados normales La pantalla Console.Out · Console.WriteLine()
stderr Salida de error Los avisos y errores La pantalla Console.Error · Console.Error.WriteLine()

Los dos de salida van a la pantalla, pero son canales distintos, y esa diferencia permite separar los resultados de los mensajes de error.

Mostrar datos con formato

using System.Globalization;

var equipos = new List<Equipo> { new("Águilas", 45, 12), new("Lobos", 41, 5), new("Tigres", 38, -2) };

Console.WriteLine($"{"Equipo",-12}{"Pts",5}{"Dif",6}");
Console.WriteLine(new string('-', 23));
int total = 0;
foreach (Equipo e in equipos)
{
    Console.WriteLine($"{e.Nombre,-12}{e.Puntos,5}{e.Diferencia,6:+0;-0;0}");
    total += e.Puntos;
}
double media = (double)total / equipos.Count;
Console.WriteLine($"Media de puntos: {media.ToString("F2", CultureInfo.InvariantCulture)}");

Console.Error.WriteLine("Aviso: datos de ejemplo, no oficiales");
Console.WriteLine("Fin del informe");

record Equipo(string Nombre, int Puntos, int Diferencia);

Salida:

Equipo        Pts   Dif
-----------------------
Águilas        45   +12
Lobos          41    +5
Tigres         38    -2
Media de puntos: 41.33
Fin del informe

Qué conviene aprender de este ejemplo:

  • Columnas alineadas. En una cadena interpolada, {valor,-12} reserva 12 posiciones y alinea a la izquierda; {valor,5} reserva 5 y alinea a la derecha. Sin eso, las columnas quedan descuadradas.
  • Formato detrás de los dos puntos. {x:F2} escribe dos decimales; {x:+0;-0;0} es un formato con tres secciones (positivo; negativo; cero) y por eso el + aparece solo en los positivos.
  • El aviso va por otro canal. Verás que la línea «Aviso: datos de ejemplo, no oficiales» no aparece en el recuadro de arriba: se escribe en Console.Error, no en la salida estándar. En una terminal sí se vería.
Necesito... En C#
Alinear a la izquierda {nombre,-12} o nombre.PadRight(12)
Alinear a la derecha {puntos,5} o puntos.ToString().PadLeft(5)
Decimales fijos {media:F2} (o {media:0.00})
Repetir un texto new string('-', 23) (un solo carácter repetido)
Mostrar sin salto de línea Console.Write("texto")
Con signo {d:+0;-0;0}

La cultura del equipo. Los números se escriben según la configuración regional del equipo. Mira lo que pasa en un equipo configurado en español:

double media = 124.0 / 3;
Console.WriteLine($"con la cultura del equipo: {media:F2}");
Console.WriteLine($"con la cultura invariable: {media.ToString("F2", System.Globalization.CultureInfo.InvariantCulture)}");

Salida:

con la cultura del equipo: 41,33
con la cultura invariable: 41.33

En un Windows en español el separador decimal es la coma. Si necesitas siempre el punto (para un archivo que lee otro programa, por ejemplo), pasa CultureInfo.InvariantCulture, como en el ejemplo anterior. Al revés, para mostrar al usuario un número en su formato, deja la cultura por defecto.

Salida estándar y salida de error

Separarlas sirve para que un programa pueda guardar los resultados en un archivo sin mezclarlos con los errores. En una terminal, el operador > redirige la salida normal y 2> la de error:

dotnet run cons1.cs > resultados.txt 2> errores.txt

Con el ejemplo anterior, resultados.txt contendría la tabla y la media y errores.txt solo el aviso. Para dar entrada desde un archivo en lugar del teclado se usa < (dotnet run programa.cs < entrada.txt), y para encadenar programas, la tubería |.

Leer datos

Se lee con Console.ReadLine(), que devuelve un string?: es null cuando ya no queda entrada (fin de archivo), por eso hay que contemplarlo. A diferencia de Java o Python, no lanza ninguna excepción al llegar al final. Para escribir sin saltar de línea, Console.Write.

Console.WriteLine("Escribe líneas (una vacía para terminar):");
int lineas = 0;
int palabras = 0;
string? linea;
while ((linea = Console.ReadLine()) is not null && linea != "")
{
    lineas++;
    palabras += linea.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
Console.WriteLine($"Líneas: {lineas}");
Console.WriteLine($"Palabras: {palabras}");

Salida:

Escribe líneas (una vacía para terminar):
Líneas: 2
Palabras: 6

Este programa lee líneas hasta encontrar una vacía (o hasta que se acabe la entrada). Aquí se le han dado hola mundo, esto es una prueba y una línea vacía. Fíjate en la condición del while: asigna la línea leída, comprueba que no sea null y que no esté vacía, todo a la vez.

Una entrada del usuario es siempre texto: si necesitas un número, conviértelo con int.TryParse y prevé que falle (mira el patrón de «pedir hasta que sea válido» en 2.3 Excepciones).

Console.ReadKey() y la entrada redirigida

Console.ReadKey() espera una tecla de la consola de verdad. Si la entrada viene de un archivo o de una tubería (< entrada.txt), lanza una InvalidOperationException. Para programas que deban funcionar con entrada redirigida, usa ReadLine.

Errores frecuentes

Error Cómo evitarlo
Columnas descuadradas Fijar un ancho para cada columna ({x,-12}, {x,5})
Mezclar resultados y errores en la misma salida Escribir los avisos con Console.Error
Dar por hecho que siempre hay una línea más que leer Comprobar null en ReadLine()
Usar el número leído sin convertirlo ni validarlo int.TryParse y volver a pedir
Decimales con coma en unas máquinas y con punto en otras Fijar CultureInfo.InvariantCulture al formatear para otros programas
ReadKey con la entrada redirigida Usar ReadLine

Para practicar

Haz los ejercicios de U7.1 · Consola. Para ver cómo se escribe en otro lenguaje: Pasar de uno a otro.