source или ./script.sh — почему скрипт отработал, а ничего не изменилось

Ты написал скрипт из двух строк: перейти в папку и задать переменную. Запустил. Ошибок нет. А ты всё там же, и переменная пустая:
$ cat t.sh
cd /tmp
export FOO=bar
$ ./t.sh
$ pwd
/home/me/project
$ echo "FOO=$FOO"
FOO=
Скрипт отработал честно. Просто всё случилось не в твоём терминале. А вот так случится в твоём:
$ source ./t.sh
$ pwd
/tmp
$ echo "FOO=$FOO"
FOO=bar
В чём разница между source и запуском
Запуск создаёт новый процесс: отдельный bash со своей папкой и своими переменными. Закончился скрипт — закончился и этот bash вместе со всем, что он менял.
source читает файл и выполняет его строки прямо в твоём текущем терминале, как будто ты набрал их руками.
Сравним по пунктам:
- Где идёт работа.
./t.sh— в дочернем процессе.source t.sh— в текущем. - Что остаётся после. Запуск не оставляет ничего: ни
cd, ни переменных, ни функций.sourceоставляет всё. - Какие переменные видит. Запущенный скрипт видит только переменные окружения, то есть то, что ты сделал через
export.sourceвидит и обычные:A=1; ./k.shнапечатает пусто, аsource k.sh— единицу. - Нужны ли права.
./t.shтребует права на запуск.sourceтолько читает файл, поэтому ему хватит и обычных прав на чтение. - Что делает
exit. В запущенном скрипте — завершает скрипт. Вsource— закрывает твой терминал. Вместо него пишиreturn. - Как работает
set -e. В запущенном скрипте — останавливает скрипт. Черезsourceэта настройка остаётся в твоём терминале, и он закроется при первой же ошибке.
Почему запуск не может изменить твой терминал
Смотри, это не настройка и не баг. Новый процесс получает копию окружения родителя. Копию, а не доступ.
Скрипт может как угодно менять свою копию. В твой терминал ничего не вернётся. Обратного канала для переменных просто нет. И папка у каждого процесса своя.
Представь, что тебе выдали фотокопию документа. Можешь исписать её вдоль и поперёк, а оригинал останется чистым.
Где ты с этим встречаешься
Этим объясняются сразу несколько вещей, которые ты делаешь по привычке:
source .venv/bin/activate. Весь смысл активации в том, чтобы поменятьPATHтвоего терминала. Запуском этого не сделать. Поэтому файлactivateдаже не помечен как исполняемый.nvm use 20.nvm— это не программа, а функция внутри твоего shell. По той же причине: программа не смогла бы поменять твойPATH.source ~/.zshrc. Применяет новые настройки здесь и сейчас, без открытия нового окна.
Один частый промах с .env. Файл со строками API=1 через source создаст обычную переменную, а дочерние программы её не увидят:
$ source .env
$ bash -c 'echo [$API]'
[]
$ set -a; source .env; set +a
$ bash -c 'echo [$API]'
[1]
Команда set -a автоматически экспортирует всё, что объявлено после неё.
И предупреждение: source выполняет файл как код. Строка $(rm -rf ~) внутри .env сработает. Чужие файлы так не подключай.
Почему это ломает ИИ-агентов
Вот ситуация из свежей практики. Агент делает source .venv/bin/activate одним шагом, а pytest — следующим. И получает command not found или системный Python.
Причина та же самая. Во многих агентах каждая команда запускается в новом shell, и активация умирает вместе с ним. Лечится просто, и это можно прямо написать агенту в правилах:
- Склей в одну команду:
source .venv/bin/activate && pytest. Здесь важно именно &&, а не точка с запятой: если активация упала, тесты не запустятся. - Или не активируй вообще:
.venv/bin/pytest. Этот путь работает откуда угодно.
Что выбрать
Выбор здесь не дело вкуса, а прямое следствие цели.
- Запускай
./script.shпо умолчанию. Сборка, деплой, бэкап и всё, что должно отработать и уйти. Скрипт изолирован и не засорит твой терминал. - Бери
sourceтолько тогда, когда цель — изменить сам терминал. Переменные,PATH, текущую папку, алиасы, функции.
Проверочный вопрос один: должно ли что-то измениться в твоём окне после завершения? Если нет, запускай. Если да, source.
Чем точка отличается от source?
Ничем: . ./t.sh и source ./t.sh в bash — одно и то же. Но source есть не везде: в простом sh на Debian и Ubuntu ты получишь source: not found, там работает только точка.
Чем bash script.sh отличается от ./script.sh?
Оба запускают отдельный процесс. bash script.sh не требует права на запуск и игнорирует первую строку с интерпретатором. ./script.sh требует права и слушается этой строки.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





