Перейти до основного вмісту

Чому я замінив Fish на Nushell

Я замінив Fish на Nushell як щоденну оболонку заради структурованих даних і типізованих конвеєрів. Для окремих завдань залишив jq, yq, rg і fd.

NushellFishDeveloper tools

Я замінив Fish на Nushell як оболонку входу в macOS. Fish уже був зручним. Він мав хороші доповнення, прив’язки клавіш у стилі vi, Starship і скорочення, якими я користуюся щодня. Nu мав зберегти це відчуття й дати мені зручніший спосіб працювати з виводом команд.

Перехід виправдав себе, бо модель даних Nu раз у раз стає в пригоді у звичайних командах і невеликих скриптах. Тепер оболонкою входу в моєму обліковому записі macOS є Nu, а Fish досі встановлений і навіть надає кілька доповнень, які Nu не може згенерувати сам.

Fish встановив високу планку

Fish уже давав мені потрібні можливості інтерактивної оболонки. Його автопідказки, підсвічування синтаксису й доповнення (opens in a new tab) (відкриється в новій вкладці) працювали добре, а моя конфігурація додавала Starship, історію Atuin, Zoxide, Mise і власні прив’язки клавіш. Згенеровані інтеграції я кешував, щоб оболонка запускалася швидко.

У конфігурації Fish списки також проходили через eza: ls показував піктограми й застосовував корисний порядок сортування, а ll додавав стан Git до детального подання. Ці псевдоніми давали зручний для читання вивід, але для подальшої фільтрації я мав розбирати текст або викликати іншу команду, яка повертала машинозчитуваний формат.

Репозиторій із моєю конфігурацією Nushell існує з 12 серпня 2025 року. Він залишався експериментом до 3 серпня 2026 року, коли я змінив оболонку входу для облікового запису й закомітив більший набір змін із назвою make Nushell viable as a daily shell. Наступні кілька днів я закривав прогалини в доповненнях, налаштовував кеші запуску й поведінку рядка запрошення та вносив невеликі виправлення сумісності.

Рядки, з якими можна працювати далі

Команди Nu передають типізовані значення через конвеєри (opens in a new tab) (відкриється в новій вкладці). Серед цих значень можуть бути записи, списки, таблиці, дати, тривалості й розміри файлів. Зовнішні команди й далі повертають текст або байти, тому звичні конвеєри Unix продовжують працювати. Найбільше користі я отримую, коли команда Nu вже повертає дані з відомою структурою.

ls повертає таблицю. Я можу відфільтрувати каталоги, відсортувати їх за колонкою modified і вибрати потрібні колонки без розбору текстового виводу:

Нещодавні каталогиnu
ls
| where type == dir
| sort-by modified --reverse
| select name modified

У конфігурації Nu я навмисно залишив вбудовані ls і open. ls залишається командою Nu за замовчуванням. Я можу запускати eza напряму, а ll використовує його, коли мені потрібні піктограми й стан Git. Я ще вирішую, чи вистачить мені стандартного ls для щоденної роботи. Коли хочу продовжити роботу з результатом, використовую його структуровані рядки. open package.json читає JSON як запис, а решта конвеєра працює з полями:

Скрипти пакета у вигляді таблиціnu
open package.json
| get scripts
| transpose name command
| sort-by name

У Nu є вбудовані конвертери для JSON, TOML, YAML, CSV та інших форматів. Кожен конвертер очікує структуру значення, сумісну з цільовим форматом. У форматах, якими я користуюся, перехід від from json до to toml більше схожий на вибір типу виводу, ніж на складання ще одного текстового конвеєра.

Таблиця з CLI стає конфігурацією Codex

Я вже пробував Nushell як щоденну оболонку. Приклад із codex features list допоміг мені оцінити модель даних Nu й залишитися з Nushell. Codex виводить список можливостей як вирівняний текст. Для такого виводу я використовую detect columns:

Скопіювати вимкнені можливості Codex як TOMLnu
^codex features list
| detect columns --guess --no-headers
| rename name stage enabled
| update enabled { into bool }
| where stage in ['under development' stable] and enabled == false
| reduce --fold {} {|feature, config|
    $config | upsert $feature.name true
  }
| to toml
| pbcopy

detect columns перетворює текст на рядки, rename дає колонкам зрозумілі назви, а update перетворює enabled із тексту на логічні значення. where залишає вимкнені можливості з двома потрібними статусами. reduce формує один запис, ключами якого стають назви можливостей, а to toml створює рядки, які я можу вставити під [features] у ~/.codex/config.toml. pbcopy копіює отриманий рядок у буфер обміну macOS.

detect columns вгадує структуру за розташуванням (opens in a new tab) (відкриється в новій вкладці), тому я використовую його як адаптер для стабільного текстового виводу. Якщо CLI підтримує JSON, я вибираю його. Тоді решта конвеєра працює з тими самими записами й фільтрами та не залежить від пробілів між колонками.

Паралельні оновлення без каші у виводі

Через update-tools я однією командою оновлюю Homebrew, Mise, Claude, Codex і глобальні Skills:

~/.config/nushell/modules/utilities.nunu
export def update-tools [] {
  let jobs = [
    {
      name: Homebrew
      commands: [
        {|| brew upgrade -y }
        {|| brew cleanup }
      ]
    }
    {
      name: Mise
      commands: [
        {|| mise upgrade }
      ]
    }
    {
      name: Claude
      commands: [
        {|| claude update }
      ]
    }
    {
      name: Codex
      commands: [
        {|| codex update }
      ]
    }
    {
      name: Skills
      commands: [
        {|| skills update -g -y }
      ]
    }
  ]
 
  $jobs | par-each --threads 5 { |job|
    let results = (
      $job.commands
      | each { |command|
          with-env {
            CLICOLOR_FORCE: "1"
            FORCE_COLOR: "1"
            HOMEBREW_COLOR: "1"
          } {
            do $command o+e>| complete
          }
        }
    )
 
    let output = (
      $results
      | get stdout
      | str join
      | str trim
    )
 
    let succeeded = (
      $results
      | all { |result| $result.exit_code == 0 }
    )
 
    let heading = if $succeeded {
      $"(ansi green_bold)✓ ($job.name)(ansi reset)"
    } else {
      $"(ansi red_bold)✗ ($job.name)(ansi reset)"
    }
 
    print $"\n($heading)\n($output)"
  } | ignore
}

Зовнішній список складається із записів. У кожному є назва для виводу й одне або кілька замикань із командами. par-each --threads 5 запускає п’ять записів із завданнями паралельно, а внутрішній each зберігає послідовність команд у межах одного завдання. Homebrew запускає очищення після оновлення. Mise, Claude, Codex і Skills не чекають на цю пару.

o+e>| complete об’єднує потоки виводу команди, запускає замикання й повертає структурований результат із полями stdout та exit_code. Функція з’єднує вивід кожного завдання, перевіряє всі коди завершення й друкує для нього окремий зелений або червоний блок. Блоки можуть з’являтися в порядку завершення, оскільки без окремої вказівки par-each не зберігає порядок вхідних даних.

Fish уміє запускати фонові завдання через & і чекати на них. У Nu весь облік лишається частиною тієї самої функції: завдання зберігаються як записи, команди як замикання, а результати мають поля. Замість довгого ланцюжка mise upgrade && claude update && codex update я запускаю незалежні частини паралельно й отримую окремий читабельний блок від кожного з п’яти інструментів.

Я зберіг решту налаштувань термінала

Я продовжую користуватися тим самим ~/.config/starship.toml. Fish завантажував згенерований вивід starship init fish, а Nu завантажує кешований starship init nu. Мій промпт із $all, кольори, стан Git і тривалість виконання команд не змінилися. Atuin, Zoxide, Mise і Carapace я теж зберіг завдяки їхнім інтеграціям із Nu.

Доповнення потребували більше роботи. Я використовую вбудовані доповнення Nu для Git і Claude, Carapace як загальний резервний варіант і невеликі окремі адаптери для прогалин. Codex і Herdr у моїх версіях можуть генерувати доповнення Fish, але не мають власних доповнень для Nu, тому моя система доповнень передає ці файли через Fish. Через це я не видалив Fish і після зміни оболонки входу.

Файли .nu я пишу в Neovim. Увімкнення додаткового модуля Nushell у LazyVim (opens in a new tab) (відкриється в новій вкладці) через :LazyExtras налаштувало мовний сервер Nushell і встановило парсер nu для Treesitter. LSP добре допомагає писати функції й знаходити помилки в замиканнях. Оскільки Neovim у мене вже був налаштований, для цього вистачило одного перемикача.

Для окремих завдань я й далі використовую jq, yq, rg і fd

Nushell замінив Fish як мою щоденну оболонку. Я зберіг спеціалізовані інструменти командного рядка, яким уже довіряв. Я використовую jq для JSON і yq для YAML, коли їхні мови запитів найзручніші для завдання або коли команда має працювати поза Nu. Через rg я шукаю у вмісті файлів, а через fd знаходжу файли. bat досі дає мені зручний для читання вивід файлів.

Я вибираю Nu, коли сама оболонка знає структуру даних: у результатах ls і open, під час роботи з датами чи розмірами файлів або у виводі CLI, який я можу розібрати на стабільні колонки. Nu також підходить для невеликих скриптів, де треба фільтрувати записи, паралельно виконувати роботу, перевіряти коди завершення й серіалізувати результат.

Nushell не сумісний із POSIX, а посібник із використання його як типової оболонки (opens in a new tab) (відкриється в новій вкладці) попереджає, що деякі програми розраховують на POSIX-сумісну оболонку входу. Підтримка доповнень для зовнішніх команд різниться. detect columns і далі вгадує структуру, а для кількох інструментів мені довелося зробити кеші й адаптери. Якщо скрипт очікує Fish, я можу запустити його однією командою.

Я можу переглядати, фільтрувати й серіалізувати списки каталогів або таблиці з виводу CLI, не зводячи їх до тексту, а update-tools керує паралельними завданнями. Цього поки що достатньо, щоб залишити Nu моєю оболонкою входу. Подивимося, як піде далі.

Джерела

433f6c5