13

I'm going through vimtutor and just learned about using :!<CMD> to execute a shell command from within vim.

I read a bit up on why not to use just :<CMD> and apparently it's because some character sequences like ls are used by vim itself.

Alright, but the first thing I tried afterwards already didn't go as expected: I typed :!cd some_directory but this had no effect, I just stayed in the current directory as confirmed by :!pwd.

However, doing :cd some_directory did change the working directory to some_directory.

I'm a bit confused here. Why didn't :!cd work?

Edit: I understand now that :! starts a new shell, but if I read correctly, so far none of the answers have explained why the :cd some_directory does change the directory. Is this a command which is executed in the shell vim itself is running in? If yes, then wouldn't one want to have an escape symbol (e.g. :|<CMD>) which always redirects commands to the original shell? This way one would have consistency in the way that both :|ls and :|cd would work as expected from a normal shell. Or what am I missing here?

7
  • 12
    ! = "not"; cd = "change directory". !cd = "don't change the directory".
    – CJ Dennis
    Feb 20, 2020 at 12:23
  • 8
    @CJDennis No. ! is vim's "run in a shell" command. It has, in this context, absolutely nothing to do with a logical NOT. By your reasoning :!ls would be "don't list files", but that command absolutely will list files.
    – marcelm
    Feb 20, 2020 at 16:09
  • 3
    No. : ! in vi runs the rest of the string as a command in a subshell. That's why the directory of the parent (vi) is unchanged. (I love the idea of a command that says "whatever I say, don't do it".) However, vi has an actual command cd that it actions by changing its own directory. But when you exit, it still cannot have changed the cwd of the shell you started vi from. Feb 20, 2020 at 16:09
  • 7
    I hear a lot of wooosh noises here in the comment section.
    – pipe
    Feb 20, 2020 at 16:51
  • 3
    @pipe Sarcasm is easy to miss in writing :P
    – marcelm
    Feb 20, 2020 at 19:32

3 Answers 3

19

I assume you are aware, generally, of the notion of parent and child processes. Particularly with respect to shells. Let's say I'm in the shell. My prompt shows the current directory ($PWD)...

[/bar] $ sh     # launch child shell
[/bar] $ cd /foo
[/foo] $ exit
[/bar] $        # back in the parent it's still /bar

Similarly...

[/bar] $ sh     # launch child shell
[/bar] $ myvar=something
[/bar] $ exit
[/bar] $ echo $myvar

[/bar] $        # parent knows nothing about myvar

Parent processes are, broadly speaking, not altered by the things that occur in their children. It is no different when Vim is the parent of a shell process.

Generally, if the parent wants to see what happens in the child the parent needs to look for it. In the case of Vim the primary way to look in at the child/shell is to capture its commands' output using the system() function or to use :r !cmds to read the output into a buffer.

As for the other version of the question that OP indicated interest in, "Why does :cd change the directory?" I'll assume "the directory" here refers to the current working directory of a subsequently launched shell ...if we were talking about Vim's working directory a fair answer to that would be "Because that's what the command does."

:cd is a Vim command operating in the Vim program space and the directory it changes is associated with the Vim process. One way this is useful is if you try to edit a file without a path (e.g. foo.txt) Vim will look in this directory for the file. A subsequently launched shell process inherits Vim's working directory because, well, that's how they've implemented it. They didn't have to do it that way. Vim is not a "shell process" it's a Vim process. But it's a natural way to do things1 and many are accustomed to partial inheritance with nested shell processes...

[/bar] $ myvar=this                # ordinary variable
[/bar] $ MYVAR=that; export MYVAR  # environment variable
[/bar] $ cd /foo                   # change current working dir
[/foo] $ sh                        # launch child shell
[/foo] $ echo $MYVAR               # c.w.d. inherited
that                               # env vars inherited
[/foo] $ echo $myvar
                                   # regular vars NOT inherited
[/foo] $ 

Running a shell command from Vim mimics such behavior.

1 In fact, most common operating systems support this behavior automatically and by default. That is when you exec or fork or whatever to launch a new process there is inheritance of some defined subset of the parent process' context/environment

4
  • 8
    echo myvar should be echo $myvar
    – user253751
    Feb 20, 2020 at 10:31
  • This explains why :!cd some_directory did not work. But why does :cd some_directory change the directory? Feb 21, 2020 at 0:36
  • Thanks for catching the typo. And I've addressed OP's question.
    – B Layer
    Feb 21, 2020 at 1:00
  • 1
    @user9007131 because :cd is a vi command that changes the directory, and just happens to be called the same as the shell command.
    – Davidmh
    Feb 21, 2020 at 13:29
8

Since vim is not a shell and you executed a shell command, you must have also executed a shell. You then told that shell to execute the cd command, which will change its working directory. The shell then terminated. The net effect of asking a shell to change its working directory and then terminate is nothing since the shell never did anything that was affected by its working directory.

You need to ask vim to change its working directory. That cannot be done by executing a shell command that changes that shell's working directory.

2
  • Then why does :cd some_directory work? Feb 21, 2020 at 0:37
  • Because that executes a vim command that tells vim to change its current directory. Executing a shell command that tells the shell to change its current directory doesn't affect vim's current directory. Feb 21, 2020 at 18:50
2

:cd is vim command :! is used to execute bash commands. In this case, cd. The cur dir is saved as variable in vim. Bash executes new session every time you do !, so it is not kept, I am quite sure.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.