2013-08-29 4 views
7

Первый сценарий:Почему pipe сбрасывает текущий рабочий каталог?

$ { mkdir dir; cd dir; pwd; } | cat; pwd; 
./dir 
. 

Второй сценарий:

$ { mkdir dir; cd dir; pwd; }; pwd; 
./dir 
./dir 

Почему это | cat оказывает влияние на текущий каталог? И как его решить? Мне нужно, чтобы первый скрипт работал точно так же, как второй. Я не хочу, чтобы cat менял текущий каталог на ..

+2

http://stackoverflow.com/questions/5760640/left-side-of-pipe-is-the-subshell, ваш 'cd' работает в подоболочке. – Mat

+0

Во втором скрипте также находится в подоболочке – yegor256

+6

@ yegor256 Нет, во втором это ** не ** в подоболочке. – devnull

ответ

11

Цитирование из manual:

Каждая команда в конвейере выполняется в своей собственной подоболочке (см. Command Execution Environment).

Также см Grouping Commands:

{}

{ list; } 

Размещение список команд между фигурными скобками вызывает список, чтобы быть выполнен в текущем контексте оболочки. Не создается подоболочка.

0

Труба не сбрасывает текущий рабочий каталог. Труба создает подоболочки в bash по одному для каждой стороны трубы. Подселочка также не сбрасывает текущий рабочий каталог. Подселочка содержит изменения в самой среде и не распространяется на родительскую оболочку.

В своем первом сценарии:

$ { mkdir dir; cd dir; pwd; } | cat; pwd; 

cd выполняется внутри левой субоболочке трубы. Рабочий каталог изменяется только в этой подоболочке. Рабочий каталог родительской оболочки не изменяется. Первый pwd выполняется в той же подоболочке, что и cd, поэтому он считывает измененный рабочий каталог. pwd в конце выполняется в родительской оболочке, поэтому он считывает рабочий каталог родительской оболочки, которая не была изменена.

В вашем втором сценарии:

$ { mkdir dir; cd dir; pwd; }; pwd; 

Там нет подоболочка создан. Кудрявые фигурные скобки не создают подоболочку. cd выполняется в родительской оболочке, и оба pwd s читают измененный рабочий каталог.

Для получения дополнительной информации и разъяснений читать штраф Баш вручную О:

1

Поведение команд трубопровода относительно переменных определяется реализацией.

Вы использовали bash, которые решили разместить каждый компонент в фоновой оболочке, так что в основной оболочке теряется эффект cd.

Если вы запустили ksh, которые решили сохранить последний элемент конвейера в текущей оболочке, и если вы положили cd в последнем утверждении, поведение было бы тем, что вы ожидаете.

$ bash 
$ { mkdir -p dir; cd dir; pwd; } | { cat; mkdir -p dir; cd dir; pwd ; } ; pwd; 
/tmp/dir 
/tmp/dir 
/tmp 
$ ksh 
$ { mkdir -p dir; cd dir; pwd; } | { cat; mkdir -p dir; cd dir; pwd ; } ; pwd; 
/tmp/dir 
/tmp/dir 
/tmp/dir 
2

Это не то, что труба идет обратно в каталог, то, что вы сделали первую команду (до запятой), применимой только к команде cat. Вы, по существу, соединяете выходные данные подпроцесса mkdir и cd и pwd, переходите к процессу cat.

Например: { mkdir dir; cd dir; pwd; } | cat; pwd;

Первый расширяется на два процесса: 1) { mkdir dir; cd dir; pwd; } | cat; и 2) pwd

Первый процесс расширяется на два процесса, { mkdir dir; cd dir; pwd; }, который затем посылает его к stdoutstdin из cat. Когда первый из этих двух процессов заканчивается и собирается stdout, его подпроцесс выходит, и это похоже на то, что cd никогда не происходило, потому что cd влияет только на каталог процесса, в котором он работал. pwd никогда не изменялся $PWD, он только печатался который был предоставлен по адресу stdin.

Чтобы решить эту проблему (при условии, я понимаю, что вы пытаетесь сделать) я хотел бы изменить это:

{ mkdir dir; cd dir; pwd; }; pwd; cd -

+1

Ваша предлагаемая замена не будет работать, так как 'cat' будет бесконечно ждать для' EOF' на своем 'stdin', который он не получит , – sanmiguel

+2

Кроме того, ваши объяснения не совсем правильные - LHS из ** трубы ** выполняется в подоболочке. В противном случае '{mkdir dir; cd dir; PWD; } | {Кот; pwd;} 'приведет к тому же' $ PWD' для обоих вызовов 'pwd'. – sanmiguel

+0

Вы оба правы. sanmiguel в особенности. Извините за это :) – manchicken

2

При запуске:

{ mkdir -p dir; cd dir; pwd; } | cat; pwd 

ИЛИ

{ mkdir -p dir; cd dir; pwd; } | date 

OR

{ mkdir -p dir; cd dir; pwd; } | ls 

При запуске группы команд на LHS трубы в суб-оболочки и, следовательно, change dirне отражается в текущей оболочке после обеих команд (LHS и RHS) в комплекте.

Однако при запуске:

{ mkdir -p dir; cd dir; pwd; }; pwd; 

Там нет трубы между следовательно, все команды в фигурных скобках и pwd вне фигурной скобки работать в самой текущей оболочки, следовательно, вы получите измененный каталог.

PS: Также обратите внимание, что эта строка:

(mkdir -p dir; cd dir; pwd;) 

Будет также не изменить текущую директорию в текущей оболочке, потому что команды в квадратных скобках выполняются в суб оболочки, тогда как фигурные скобки используются только для группировки.

+2

Было бы лучше/проще использовать 'mkdir -p dir', который не сбой, если каталог существует, а не' rmdir dir', который не работает, если каталог не существует? –

+0

@JonathanLeffler: Согласен, отредактирован. – anubhava

0

Хотя руководства говорит:

{ list; } 

Placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created. 

Размещение его на трубопроводе будет по-прежнему разместить его на субоболочке.

{ list; } | something 

С

Each command in a pipeline is executed in its own subshell (see Command Execution Environment). 

Команды в { } сам не будет размещен на субоболочке но выше контекст этого сам бы поэтому он все равно будет то же самое.

В тесте работает () и { } только будет тем же:

# echo "$BASHPID" 
6582 
# (ps -p "$BASHPID" -o ppid=) | cat 
6582 
# { ps -p "$BASHPID" -o ppid=; } | cat 
6582 

И посылает родительский процесс как вызывающие оболочки.

Смежные вопросы