如何使用zsh或bash让shell自动检测

85

我有一个问题,想知道如何确定用户正在使用哪个shell。假设有一个脚本,如果用户正在使用zsh,则将PATH放到他的.zshrc中;如果使用bash,则应该放在.bashrc中。并相应地设置rvmrc。

#!/usr/bin/env bash
export PATH='/usr/local/bin:$PATH' >> ~/.zshrc
source ~/.zshrc

我尝试了以下方法,但它没有起作用:

if [[ $0 == "bash" ]]; then
  export PATH='/usr/local/bin:$PATH' >> ~/.bashrc
elif [[ $0 == "zsh" ]]; then
  export PATH='/usr/local/bin:$PATH' >> ~/.zshrc
fi

# ... more commands ...

if [[ $0 == "bash" ]]; then
  [[ -s '/Users/`whoami`/.rvm/scripts/rvm' ]] && source '/Users/`whoami`/.rvm/scripts/rvm' >> ~/.bashrc
  source ~/.bashrc
elif [[ $0 == "zsh" ]]; then
  [[ -s '/Users/`whoami`/.rvm/scripts/rvm' ]] && source '/Users/`whoami`/.rvm/scripts/rvm' >> ~/.zshrc
  source ~/.zshrc
fi

把这个放在.profile里面不是更合理吗?这样可以跨多种shell,包括sh - tripleee
@tripleee 你好,因为这是针对bash或zsh的特定事情,所以我认为... - Juanito Fatas
1
我建议您阅读《高级Bash脚本指南》的此部分 - Spencer Rathbun
1
$0 == "-zsh" instead of $0 == "zsh" - Rui Martins
8个回答

191

如果使用的是Zsh shell,则变量$ZSH_VERSION会被定义。同样,对于Bash和$BASH_VERSION也是如此。

if [ -n "$ZSH_VERSION" ]; then
   # assume Zsh
elif [ -n "$BASH_VERSION" ]; then
   # assume Bash
else
   # assume something else
fi

然而,这些变量只告诉你正在使用哪个shell运行上面的代码。因此,您必须在用户的shell中使用source来执行此片段。

作为替代方案,您可以使用$SHELL环境变量(应包含用户首选shell的绝对路径),并从该变量的值猜测shell:

case $SHELL in
*/zsh) 
   # assume Zsh
   ;;
*/bash)
   # assume Bash
   ;;
*)
   # assume something else
esac
当`/bin/sh`是一个符号链接指向`/bin/bash`时,上面的代码将会失败。如果你想依赖于`$SHELL`变量,更安全的做法是实际执行一些代码。
if [ -n "$($SHELL -c 'echo $ZSH_VERSION')" ]; then
   # assume Zsh
elif [ -n "$($SHELL -c 'echo $BASH_VERSION')" ]; then
   # assume Bash
else
   # assume something else
fi

这个最后的建议可以从脚本运行,无论使用哪个 shell 运行该脚本。


4
你在终端中运行了zsh。从交互式的zsh环境中,你又运行了bash来解释一个脚本。在那个脚本中,你测试了哪个shell执行了这个脚本,结果是bash。 - Gilles 'SO- stop being evil'
尽管可能有用,但如果在从bash转换到zsh之前执行export BASH_VERSION,则此方法将失败。同样,在从zsh转换到bash时执行export ZSH_VERSION也是如此。即使没有导出的情况下,依赖于环境变量也是有风险的,因为它可以在shell检查之前轻松修改或取消设置。可以使用typeset -r ZSH_VERSIONtypeset -r BASH_VERSION...虽然这种方法最好只在必要时使用,实现起来困难,而且可能是不良实践。 - ShpielMeister
zsh不再设置$ZSH_VERSION了。但它确实设置了一个名为$ZSH的变量。 - agconti
@agconti,Zsh 的最新开发版本仍然设置 $ZSH_VERSION 变量,而且他们不太可能删除该变量,因为它在 Zsh 自身的许多地方都被使用。 - adl
1
@agconti 对于ohmyzsh(一组配置文件,而不是zsh本身)不设置该变量是正常的。该变量在Zsh源代码中设置 https://github.com/zsh-users/zsh/blob/master/Src/params.c#L938 ,然后在各个地方使用。 - adl
显示剩余5条评论

56

只需执行echo $0即可。 如果是zsh,则会显示-zsh,如果是bash,则会显示-bash

编辑:有时它返回-zsh,有时返回zsh,对于bash也是如此,我不知道为什么。


2
这种方法并非适用于所有的shell,但对于我来说,每次使用命令行界面时这都是我的首选 -- 它的成功率往往很高。像Fish这样的shell执行echo $0命令会返回空值。 - kmatheny
5
“-zsh” 或 “-bash” 表示使用登录 shell。如果名称前没有“-”,则用户正在使用非登录 shell。 - smac89
5
如果在函数中调用,这种方法就不起作用了,因为$0会返回函数名。 - Jools
1
这在被脚本调用时也不起作用,它会给出脚本的名称。 - Tom Kelly
detected_shell=$(echo "${0//-/}") ... You can test it like this: if [[ -f "${HOME}/.${detected_shell}rc" ]]; then echo "Found ${HOME}/.${detected_shell}rc"; fi - blizzrdof77

41
警告:您看起来提出的问题、您想问的问题和您应该问的问题是三码事。
“用户使用哪个shell”这个问题很模糊。您尝试确定的似乎是执行脚本的shell是什么。这总是会是您在脚本的#!行中放置的内容,除非您希望用户编辑该脚本,否则对您没有用处。
我认为您想问的是用户最喜欢的shell是什么。这不能完全可靠地确定,但您可以涵盖大多数情况。检查SHELL环境变量。如果它包含fishzshbashkshtcsh,那么用户最喜欢的shell可能就是那个shell。然而,这是您问题的错误答案。
文件,如 .bashrc.zshrc.cshrc 等等都是 shell 的初始化文件。它们不是定义环境变量的正确位置。在此定义的环境变量只能在用户启动该 shell 的终端中使用,而不能在从 GUI 启动的程序中使用。该定义还将覆盖用户在子会话中进行的任何自定义设置。
定义环境变量的正确位置是会话启动文件。这与用户选择的 shell 大多无关。不幸的是,没有一个单一的位置来定义环境变量。在许多系统上,~/.profile 可以工作,但这并不普遍适用。请参见 https://unix.stackexchange.com/questions/4621/correctly-setting-environment 和我在那里链接到的其他帖子,以获取更长的讨论。

14
“那将始终是您在脚本的#!行中输入的任何内容。”- 这并不正确,因为用户可以轻松地将脚本文件名作为参数传递给任何shell。 - Michael Mior
2
@MichaelMior:如果脚本没有标记为可执行文件,用户可能会将脚本文件名作为参数传递给某个随机 shell。这取决于您的安装过程是否如此。 - Kevin
7
预料之中,被接受的解决方案只提供了修辞上的告诫和责备,而没有实质性的解决方案。每个人都想要adl有权威的回答,该回答提供了几个通用片段来解决实际问题。 - Cecil Curry
@CecilCurry 我认为我的答案被接受是因为它回答了提问者想要问的问题。adl的答案更详细地解释了标题,但这通常不是正确的问题。 - Gilles 'SO- stop being evil'

7

您可以简单地尝试一下

 echo $SHELL

1
这个失败了。在Zsh中,如果我打开一个bash子shell并执行此操作,它仍然会显示/bin/zsh,显然不是该shell。 - iconoclast

5

其他答案在使用 set -u 时失效。

  if [ ! -z ${ZSH_VERSION+x} ]; then
    echo "this is zsh"
    echo ${(%):-%x}
  elif [ ! -z ${BASH_VERSION+x} ]; then
    echo "this is bash"
    echo $BASH_SOURCE
  else
    echo "not recognized"
  fi

2
如果我从zsh打开一个bash子shell,这将成功,不像echo $SHELL - iconoclast

3

这是一种替代方法,可能并不适用于所有的shell。

for x in $(ps -p $$)
do
  ans=$x
done
echo $ans

2
ps -p $$ --no-headers -o cmd 这个命令稍微好一点。 - forivall
1
除非它无法工作:ps: 未知选项 -- no-headers - Jools
ps -f -o cmd= -p "$$" | sed '1{/^$/N;s/^\n//;t r;:r;/^\[/{:l;$s/^\[\(.*\)\]$/\1/;t;N;b l}}' 应当是健壮和完整的。(sed 处理了剥离 -f 回退注释 & 非POSIX兼容的 ps 实现,当全部标题文本字段为空时仍然写入标题行。)看起来 cmd 格式名称并不标准,所以如果失败,您也可以尝试 ps -o comm= -p "$$" | sed '1{/^$/N;s/^\n//}' - bb010g

1

我自己遇到了类似的问题,最终选择了:

_shell="$(ps -p $$ --no-headers -o comm=)"                                                                                                       
if [[ $_shell == "zsh" ]]; then                                                                                                                  
    read -q -s "?Do it?: "                                                                                                                    
fi                                                                                                                                               
elif [[ $_shell == "bash" || $_shell == "sh" ]]; then                                                                                              
    read -n 1 -s -p "Do it [y/n] "                                                                                                            
fi                                                                                                                                               

1

以下是我根据Gilles之前的答案所做的:

if [ -n "$ZSH_VERSION" ]; then
  SHELL_PROFILE="$HOME/.zprofile"
else
  SHELL_PROFILE="$HOME/.bash_profile"
fi
echo "export VAR1=whatever" >> $SHELL_PROFILE
echo "INFO: Refreshing your shell profile: $SHELL_PROFILE"
if [ -n "$ZSH_VERSION" ]; then
  exec zsh --login
else
  source $SHELL_PROFILE
fi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接