简体   繁体   English

为什么管道重置当前的工作目录?

[英]Why pipe resets current working directory?

First script: 第一个脚本:

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

Second script: 第二个脚本:

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

Why this | cat 为什么这个| cat | cat has an effect on current directory? | cat对当前目录有影响吗? And how to solve it? 以及如何解决? I need the first script to work exactly as the second one. 我需要第一个脚本完全像第二个脚本一样工作。 I don't want cat to change my current directory back to . 我不希望cat将我当前的目录更改回. .

Quoting from the manual : 引自手册

Each command in a pipeline is executed in its own subshell (see Command Execution Environment ). 管道中的每个命令都在其自己的子shell中执行 (请参阅命令执行环境 )。

Also see Grouping Commands : 另请参阅分组命令

{}

  { list; } 

Placing a list of commands between curly braces causes the list to be executed in the current shell context. 在花括号之间放置命令列表会导致列表在当前shell上下文中执行。 No subshell is created. 没有创建子shell。

It's not that the pipe goes back to the directory, it's that you've made the first command (prior to the semicolon) applicable only to the cat command. 这不是管道返回到目录,而是你已经使第一个命令(在分号之前)仅适用于cat命令。 You're essentially piping the output of the subprocess of the mkdir and cd and pwd go to the cat process. 你实际上是管道mkdir的子进程的输出, cdpwd转到cat进程。

For example: { mkdir dir; cd dir; pwd; } | cat; pwd; 例如: { mkdir dir; cd dir; pwd; } | cat; pwd; { mkdir dir; cd dir; pwd; } | cat; pwd;

First expands into two processes: 1) { mkdir dir; cd dir; pwd; } | cat; 首先扩展到两个过程:1) { mkdir dir; cd dir; pwd; } | cat; { mkdir dir; cd dir; pwd; } | cat; and 2) pwd 和2) pwd

The first process expands into two processes, { mkdir dir; cd dir; pwd; } 第一个过程扩展为两个过程, { mkdir dir; cd dir; pwd; } { mkdir dir; cd dir; pwd; } { mkdir dir; cd dir; pwd; } which then sends its stdout to stdin of cat . { mkdir dir; cd dir; pwd; } ,然后将其stdoutstdincat When the first of these two processes finishes and the stdout is collected, its subprocess exits and it is like the cd never happened because the cd only affects the directory of the process it was running in. The pwd never actually changed $PWD , it only printed that which was provided on stdin . 当这两个进程中的第一个完成并收集stdout ,它的子进程退出,就像cd从未发生过,因为cd只会影响它运行的进程的目录pwd实际上从未改变$PWD ,它只是打印在stdin提供的内容。

To resolve this issue (assuming I understand what you are trying to do) I would change this to: 要解决此问题(假设我了解您要执行的操作),我会将其更改为:

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

When you run: 当你运行:

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

OR 要么

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

OR 要么

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

You are running group of commands on LHS of pipe in a sub-shell and hence change dir isn't reflected in current shell after both commands (LHS and RHS) complete. 您正在子shell中的管道LHS上运行命令组,因此完成两个命令(LHS和RHS)后, change dir 不会反映在当前shell中

However when you run: 但是当你运行时:

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

There is no pipe in between hence all the commands inside curly braces and pwd outside curly brace run in the current shell itself hence you get changed directory. 之间没有管道,因此花括号内的所有命令和花括号外的pwd 都在当前shell中运行,因此您将获得更改的目录。

PS: Also note that this line: PS:还要注意这一行:

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

Will also not change the current directory in current shell because commands inside square brackets execute in a sub shell whereas curly braces are just used for grouping. 也不会更改当前shell中的当前目录,因为方括号内的命令在子shell中执行,而花括号仅用于分组。

The behavior of pipeline commands regarding variables is implementation defined. 关于变量的管道命令的行为是实现定义的。

You happen to use bash which choose to put every component in a background shell so the cd effect is lost in the main shell. 您碰巧使用bash选择将每个组件放在后台shell中,因此cd效果在主shell中丢失。

Should you have run ksh which choose to keep the last element of a pipeline in the current shell and should you have put the cd in the last statement, the behavior would have been the one you expect. 你是否应该运行ksh选择在当前shell中保留管道的最后一个元素,如果你把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

The pipe does not reset the current working directory. 管道不会重置当前工作目录。 The pipe creates subshells, in bash one for each side of the pipe. 管道在管道的每一侧以bash形成子壳。 The subshell also does not reset the current working directory. 子shell也不会重置当前工作目录。 The subshell contains the changes to the environment in itself and does not propagate them to the parent shell. 子shell包含对环境本身的更改,并且不会将它们传播到父shell。

In your first script: 在你的第一个脚本中:

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

The cd is executed inside the left subshell of the pipe. cd在管道的左子shell内执行。 The working directory is changed only in that subshell. 工作目录仅在子shell中更改。 The working directory of the parent shell is not changed. 父shell的工作目录不会更改。 The first pwd is executed in the same subshell as the cd so it reads the changed working directory. 第一个pwd在与cd相同的子shell中执行,因此它读取已更改的工作目录。 The pwd at the end is executed in the parent shell so it reads the working directory of the parent shell which was not changed. 最后的pwd在父shell中执行,因此它读取未更改的父shell的工作目录。

In your second script: 在你的第二个脚本中:

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

There is no subshell created. 没有创建子shell。 The curly braces do not create a subshell. 花括号不会创建子壳。 The cd is executed in the parent shell and both pwd s read the changed working directory. cd在父shell中执行,两个pwd读取已更改的工作目录。

For more information and explanation read the fine bash manual about: 有关更多信息和解释,请阅读精细bash手册:

Although the manuals says: 虽然手册说:

   { list; }

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

Placing it on a pipeline would still place it on a subshell. 将它放在管道上仍然会将它放在子壳上。

   { list; } | something

Since 以来

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

The commands in { } itself would not be placed on a subshell but the higher context of it itself would be that's why it would still be the same. { }本身中的命令不会放在子shell上,但它本身的更高上下文将是它仍然是相同的原因。

As a test running ( ) and { } would just be the same: 作为测试,running ( ){ }将是相同的:

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

Both sends parent process as the calling shell. 两者都将父进程作为调用shell发送。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM