Почему C# идеален для CLI-инструментов, и почему это важно для Python-разработчика
Когда мы говорим о создании консольных утилит, в голову первым делом приходят Python, Bash или Go. Но что, если я скажу вам, что C# — один из самых недооценённых языков для написания быстрых, кроссплатформенных и красивых CLI-инструментов? Да-да, тот самый C#, который многие ассоциируют с веб-формами и корпоративными монстрами.
С выходом .NET 6/8, C# обзавёлся минимальным API, нативной компиляцией (AOT) и невероятно удобной библиотекой System.CommandLine. Теперь создать утилиту, которая работает на Windows, Linux и macOS, можно буквально за 15 минут. Для студентов и разработчиков, изучающих Python, это отличный способ расширить инструментарий и понять, как устроены CLI-утилиты на других языках.
В этой статье мы не просто напишем "Hello World". Мы создадим полноценную утилиту для работы с файлами, которая умеет парсить аргументы, выводить цветной текст, обрабатывать ошибки и даже показывать прогресс-бар. Поехали!
Настройка проекта: минимум телодвижений
Первое, что нужно сделать — убедиться, что у вас установлен .NET SDK (версия 6.0 или выше). Проверить просто:
dotnet --version
Если всё ок, создаём новый консольный проект:
dotnet new console -n FileGuardian
cd FileGuardian
Уже на этом этапе мы получаем готовый скелет. Но для настоящей CLI-утилиты нам понадобится библиотека для парсинга аргументов. Добавляем её:
dotnet add package System.CommandLine --version 2.0.0-beta4.22272.1
Готово. Теперь у нас есть всё для создания профессионального консольного приложения на C#.
Почему System.CommandLine, а не ручной парсинг?
Многие новички пишут args[0], args[1] и ловят IndexOutOfRangeException. Это путь боли. System.CommandLine даёт:
- Автоматический парсинг флагов (
--output,-o) - Подсказки при неверном вводе
- Встроенную генерацию
--help - Поддержку подкоманд (как в git)
Это must-have для любой серьёзной консольной утилиты C#.
Пишем ядро утилиты: первая команда
Представьте, что мы создаём инструмент для мониторинга файлов — FileGuardian. Он будет проверять размер файлов и предупреждать, если они превышают лимит. Звучит полезно, правда?
Откроем Program.cs и напишем следующее:
using System.CommandLine;
var rootCommand = new RootCommand("FileGuardian — утилита для мониторинга файлов");
var fileOption = new Option<FileInfo?>(
name: "--file",
description: "Путь к файлу для анализа"
);
rootCommand.AddOption(fileOption);
rootCommand.SetHandler((file) =>
{
if (file is null || !file.Exists)
{
Console.Error.WriteLine("❌ Файл не найден!");
return;
}
long sizeInBytes = file.Length;
double sizeInMb = sizeInBytes / (1024.0 * 1024.0);
Console.WriteLine($"📄 Файл: {file.Name}");
Console.WriteLine($"📏 Размер: {sizeInMb:F2} MB");
if (sizeInMb > 100)
Console.WriteLine("⚠️ Внимание: файл превышает 100 MB!");
}, fileOption);
await rootCommand.InvokeAsync(args);
Запускаем:
dotnet run -- --file somefile.txt
Уже работает. Но это только начало. Давайте добавим изюминку — цветной вывод и прогресс-бар.
Красивый вывод: добавляем цвета и эмодзи
Консоль не обязана быть чёрно-белой. C# позволяет легко менять цвета текста. Создадим вспомогательный класс:
public static class ConsoleHelper
{
public static void WriteSuccess(string message)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"✅ {message}");
Console.ResetColor();
}
public static void WriteError(string message)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"❌ {message}");
Console.ResetColor();
}
public static void WriteWarning(string message)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"⚠️ {message}");
Console.ResetColor();
}
}
Теперь наш код обработки файла станет информативнее. Замените строки с Console.WriteLine на вызовы ConsoleHelper.WriteSuccess() и ConsoleHelper.WriteWarning(). Пользователь сразу увидит, всё ли в порядке.
Это превращает CLI-утилиту на C# в продукт, которым приятно пользоваться.
Продвинутый парсинг: подкоманды и несколько аргументов
Хорошая утилита должна уметь делать несколько вещей. Добавим вторую команду — поиск дубликатов файлов по размеру. Для этого используем подкоманды:
var checkCommand = new Command("check", "Проверить размер файла");
checkCommand.AddOption(fileOption);
checkCommand.SetHandler((file) => { /* код проверки */ }, fileOption);
var findCommand = new Command("find", "Найти дубликаты по размеру");
var directoryOption = new Option<DirectoryInfo?>(
name: "--dir",
description: "Директория для поиска"
);
findCommand.AddOption(directoryOption);
findCommand.SetHandler((dir) =>
{
if (dir is null || !dir.Exists)
{
ConsoleHelper.WriteError("Директория не найдена");
return;
}
var files = dir.GetFiles("*", SearchOption.AllDirectories);
var groups = files.GroupBy(f => f.Length)
.Where(g => g.Count() > 1);
foreach (var group in groups)
{
ConsoleHelper.WriteWarning($"Размер: {group.Key} байт");
foreach (var file in group)
Console.WriteLine($" - {file.FullName}");
}
}, directoryOption);
rootCommand.AddCommand(checkCommand);
rootCommand.AddCommand(findCommand);
Теперь наша утилита C# CLI поддерживает две команды:
dotnet run -- check --file data.txtdotnet run -- find --dir ./documents
Выглядит как настоящий профессиональный инструмент, не правда ли?
Обработка ошибок: делаем утилиту надёжной
Одна из самых частых проблем начинающих — игнорирование исключений. Пользователь вводит неверный путь, и программа падает с StackTrace. Это убивает доверие. Давайте добавим централизованную обработку:
<