20

I just found a way to start zsh when I start the bash on Windows from

https://www.howtogeek.com/258518/how-to-use-zsh-or-another-shell-in-windows-10/.

It recommended to add following code at the last of .bashrc.

# Launch Zsh
if [ -t 1 ]; then
exec zsh
fi

What does [ -t 1 ] mean?

Is it just true?

Then, can I just do this?

exec zsh
Jin Kwon
  • 480
  • 1
  • 5
  • 14

3 Answers3

23

[] is shortcut of test command.

According to man test:

-t FD
True if FD is a file descriptor that is associated with a terminal.

So if you running bash as interactive shell (terminal - see this thread for terminology explanation), bash will be replaced by zsh.

More about .bash* files:

When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable. The --noprofile option may be used when the shell is started to inhibit this behavior.

When a login shell exits, bash reads and executes commands from the files ~/.bash_logout and /etc/bash.bash_logout, if the files exists.

When an interactive shell that is not a login shell is started, bash reads and executes commands from ~/.bashrc, if that file exists. This may be inhibited by using the --norc option. The --rcfile file option will force bash to read and execute commands from file instead of ~/.bashrc.

Stéphane Chazelas comment:
Note that a shell can be interactive without stdout being a terminal, and a shell can be non-interactive with a terminal on stdout (like anytime you run a script within a terminal without redirecting/piping its output), and bash can read .bashrc even when not interactive (like in ssh host cmd where bash is the login shell of the user on host, or bash --login -c 'some code'). case $- in *i*)... is the correct way to test if a shell is interactive.

mrc02_kr
  • 1,973
  • 17
  • 31
  • 5
    Note that a shell can be interactive without stdout being a terminal, and a shell can be non-interactive with a terminal on stdout (like anytime you run a script within a terminal without redirecting/piping its output), and `bash` can read `.bashrc` even when not interactive (like in `ssh host cmd` where `bash` is the login shell of the user on host, or `bash --login -c 'some code'` where the `.bash_profile` sources the `.bashrc`). `case $- in *i*)...` is the correct way to test if a shell is interactive. – Stéphane Chazelas Aug 31 '17 at 13:34
  • @StéphaneChazelas good point. I will contain your comment in answer – mrc02_kr Aug 31 '17 at 13:35
  • There are a number of different definitions of "interactive". If the shell _thinks_ it is interactive, then `i` will be set (and in modern shells that can be tested with `if` instead of having to use `case`). But there are many use cases where one only cares if stdout (or stdin, or stderr...) is attached to a terminal. – Mark Reed Nov 21 '17 at 15:54
15

The test command [ -t 1 ] checks whether bash's output is on a terminal. The intent of this line is clearly to run zsh when opening a terminal, without disrupting other uses of bash. But it's done very badly.

The file .bashrc is read in three circumstances:

  • When bash is executed as an interactive shell, i.e. to run commands typed by the user rather than to execute batch commands.
  • When bash is a non-interactive shell which is run by an RSH or SSH daemon (typically because you run ssh host.example.com somecommand and bash is your login shell on host.example.com).
  • When it's invoked explicitly, e.g. in a user's .bash_profile (bash's choice of startup files is a bit weird).

[ -t 1 ] is a poor way to detect interactive shells. It's possible, but rare, to run bash interactively with standard output not going to a terminal. It's more common to have standard output going to a terminal in a non-interactive shell; a non-interactive shell has no business running .bashrc but unfortunately bash shells invoked by SSH do. There's a much better way: bash (and any other sh-style shell) provides a built-in, reliable method to do it.

case $- in
  *i*) echo this shell is interactive;;
  *) echo this shell is not interactive;;
esac

So “launch zsh if this is an interactive shell” should be written

case $- in
  *i*) exec zsh;;
esac

But even that is not a good idea: it prevents opening a bash shell, which is useful even if you use zsh. Forget about this blog post and instead simply configure your shortcut that opens a terminal to run zsh instead of bash. Don't arrange things so that “whenever you open the Bash application on Windows, it will now start up with the Zsh shell”: when you want zsh, open the Zsh application.

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • For `rsh`/`ssh` and for interactive shell, it's if it's **not** a login shell. For login shells (`sshd` would not start a non-interactive login shell, but you could do it with `ssh host exec bash -l`), `.bash_profile` is read instead. Also note that for `rsh`/`ssh`, you also need `$SHLVL` to be unset or 0. – Stéphane Chazelas Sep 04 '17 at 14:54
  • I suggest to use `[[ -x /usr/bin/zsh && $- = *i* ]] && exec /usr/bin/zsh` instead as it will checks first if `zsh` is installed (actually if there is executable`/usr/bin/zsh`) then so switch to `zsh` otherwise we will fail to login to bash terminal if that's not installed. – αғsнιη Nov 19 '20 at 09:25
6

man 1 test:

-t FD

file descriptor FD is opened on a terminal

Your example executes (replaces running process, in this case bash) with zsh on if stdout is open on a terminal (not a file/pipe/etc).

sebasth
  • 14,332
  • 4
  • 50
  • 68