CLI AI

sed -i не работает на Mac? Различия BSD и GNU

2026-05-15

Если вы получили sed: 1: "s/foo/bar/g": invalid command code, ничего не сломалось. В macOS стоит BSD-окружение, а скрипт написан под GNU. Имена команд те же, флаги разные. Вот шесть различий, которые реально кусаются.

1. sed -i на macOS требует аргумент

clai
$ clai замени foo на bar прямо в файле config.txt, чтобы работало и на маке, и на линуксе→ sed -i.bak 's/foo/bar/g' config.txt   # переносимо: создаст config.txt.bak

В GNU суффикс бэкапа необязателен, в BSD — обязателен. sed -i '' 's/…' работает на macOS и падает на Linux. sed -i.bak работает везде — просто останется бэкап, который надо удалить.

2. Расширенные регулярки: только -E, никогда -r

clai
$ clai замени любую последовательность цифр на N через расширенные регулярки→ echo "foo123" | sed -E 's/[0-9]+/N/'fooN

-r есть только в GNU. -E понимают обе версии — GNU принимает его уже много лет. Сделайте -E привычкой, и целый класс ошибок исчезнет.

3. date: -d на Linux, -r на macOS

clai
$ clai преобразуй unix-время 1735689600 в читаемую дату в UTC→ date -u -d @1735689600 '+%Y-%m-%d %H:%M:%S UTC'    # GNU2025-01-01 00:00:00 UTC→ date -u -r 1735689600 '+%Y-%m-%d %H:%M:%S UTC'     # BSD / macOS

Переносимого варианта здесь нет. Либо ветвитесь по uname, либо ставьте GNU coreutils и вызывайте gdate.

4. stat: -c на Linux, -f на macOS

clai
$ clai выведи размер и имя этого файла→ stat -c '%s %n' t.txt      # GNU4 t.txt→ stat -f '%z %N' t.txt      # BSD / macOS

Отличается и флаг, и спецификаторы формата. Если скрипт делает со stat что-то нетривиальное, возьмите find -printf на Linux или wc -c там, где нужна переносимость.

5. grep -P на macOS не существует

clai
$ clai вытащи из этой строки только цифры, переносимым способом→ echo "abc123" | grep -Eo '[0-9]+'123

grep -P (perl-регулярки) — опция сборки GNU, в macOS её не поставляют. -E закрывает большинство реальных шаблонов; если действительно нужен lookahead, вам нужен perl или ripgrep, а не grep.

6. xargs -r есть только в GNU — и он вам нужен

clai
$ clai найди .log старше 30 дней и удали их, не запуская rm на пустом списке→ find . -name '*.log' -mtime +30 -print0 | xargs -0 -r rm

Без -r GNU-шный xargs на пустом вводе запускает команду один раз вообще без аргументов — именно так xargs rm в пустом каталоге превращается в ошибку, а xargs docker rm — в сюрприз. BSD-шный xargs и так не запускается на пустом вводе, поэтому там -r не нужен, а в старых версиях ещё и не распознаётся.

Грабли

  • brew install coreutils ставит близнецов с префиксом g — gsed, gdate, gstat. Если добавить gnubin в PATH, мак начнёт вести себя как Linux: у вас всё почините, а от остальной команды баг спрячете.
  • На readlink -f полагаться нельзя. В старых macOS его нет. Берите realpath, где он есть, или greadlink -f.
  • base64 -d против -D. GNU декодирует через -d, macOS исторически хочет -D.

Похожие вопросы

Что вообще значит «invalid command code»? BSD-шный sed съел следующее слово как суффикс бэкапа, а остаток вашего выражения прочитал как команду, которой не знает. Это жертва разбора аргументов, а не проблема регулярки.

macOS — это BSD или Linux? Ни то, ни другое. Пользовательское окружение ведёт родословную от FreeBSD, поэтому имена утилит совпадают с линуксовыми, а флаги — нет.

Как написать скрипт, который работает и там, и там? Держитесь POSIX-флагов, предпочитайте -E, всегда пишите sed -i.bak и проверяйте на обеих системах до того, как это попадёт в CI.

Читайте также

CliAI определяет вашу ОС и оболочку и генерирует те флаги, которые эта машина действительно принимает. Установка одной строкой.