如何确定我正在使用的当前 shell?
仅命令的输出ps
就足够了吗?
如何在不同风格的 Unix 中做到这一点?
有三种方法可以找到当前 shell 的可执行文件的名称:
请注意,如果 shell 的可执行文件是 ,则所有三种方法都可能被愚弄/bin/sh
,但它实际上是重命名的bash
,例如(经常发生)。
因此,您的第二个关于输出是否可行的问题是用“不总是ps
”来回答的。
echo $0
- 将打印程序名称......在外壳的情况下是实际的外壳。
ps -ef | grep $$ | grep -v grep
- 这将在正在运行的进程列表中查找当前进程 ID。由于当前进程是shell,所以会包含在内。
这不是 100% 可靠的,因为您可能有其他进程的ps
列表包含与 shell 的进程 ID 相同的数字,特别是如果该 ID 是一个小数字(例如,如果 shell 的 PID 为“5”,您可能会发现进程称为“java5”或“perl5”在同一grep
输出!)。这是“ps”方法的第二个问题,除了不能依赖 shell 名称之外。
echo $SHELL
- 当前 shell 的路径存储SHELL
为任何 shell 的变量。对此的警告是,如果您将 shell 显式启动为子进程(例如,它不是您的登录 shell),您将获得登录 shell 的值。如果有可能,请使用ps
or$0
方法。
但是,如果可执行文件与您的实际 shell 不匹配(例如/bin/sh
实际上是 bash 或 ksh),您需要启发式方法。下面是一些特定于各种 shell 的环境变量:
$version
在 tcsh 上设置
$BASH
设置在 bash
$shell
(小写)设置为 csh 或 tcsh 中的实际 shell 名称
$ZSH_NAME
在 zsh 上设置
ksh 有$PS3
和$PS4
设置,而普通的 Bourne shell ( sh
) 只有$PS1
和$PS2
设置。这通常似乎是最难区分的——我们在 Solaris boxen 上安装的整个环境变量集的唯一区别是, , , , , , ,和。sh
ksh
$ERRNO
$FCEDIT
$LINENO
$PPID
$PS3
$PS4
$RANDOM
$SECONDS
$TMOUT
尝试
ps -p $$ -oargs=
或者
ps -p $$ -ocomm=
如果您只想确保用户使用 Bash 调用脚本:
if [ -z "$BASH" ] ;then echo "Please run this script $0 with bash"; exit 1; fi
你可以试试:
ps | grep `echo $$` | awk '{ print $4 }'
或者:
echo $SHELL
$SHELL
不必总是显示当前的 shell。它仅反映要调用的默认 shell。
要测试上述内容,假设bash
是默认 shell,try echo $SHELL
,然后在同一个终端中,进入其他 shell(例如KornShell (ksh))并尝试$SHELL
。在这两种情况下,您都会看到 bash 的结果。
要获取当前 shell 的名称,请使用cat /proc/$$/cmdline
. 以及 shell 可执行文件的路径readlink /proc/$$/exe
。
有很多方法可以找出 shell 及其对应的版本。这里有一些对我有用的。
直截了当
骇人听闻的方法
$> ******* (输入一组随机字符,在输出中您将获得 shell 名称。在我的情况下-bash: chapter2-a-sample-isomorphic-app: command not found)
ps是最可靠的方法。不保证会设置 SHELL 环境变量,即使设置了,也很容易被欺骗。
我有一个简单的技巧来找到当前的 shell。只需键入一个随机字符串(这不是命令)。它将失败并返回“未找到”错误,但在行首它会说它是哪个 shell:
ksh: aaaaa: not found [No such file or directory]
bash: aaaaa: command not found
我尝试了许多不同的方法,对我来说最好的方法是:
ps -p $$
它也可以在Cygwin下工作,并且不能像 PID grepping 那样产生误报。通过一些清理,它只输出一个可执行名称(在 Cygwin 下,带有路径):
ps -p $$ | tail -1 | awk '{print $NF}'
您可以创建一个函数,这样您就不必记住它:
# Print currently active shell
shell () {
ps -p $$ | tail -1 | awk '{print $NF}'
}
...然后执行shell
.
它在 Debian 和 Cygwin 下进行了测试。
以下将始终给出实际使用的 shell - 它获取实际可执行文件的名称而不是 shell 名称(即,ksh93
而不是ksh
等)。对于/bin/sh
,它将显示使用的实际外壳,即dash
。
ls -l /proc/$$/exe | sed 's%.*/%%'
我知道有很多人说ls
永远不应该处理输出,但是您使用的外壳以特殊字符命名或放置在以特殊字符命名的目录中的概率是多少?如果情况仍然如此,那么还有很多其他不同做法的例子。
正如Toby Speight所指出的,这将是实现相同目标的更合适和更清洁的方法:
basename $(readlink /proc/$$/exe)
我打印父进程的变体:
ps -p $$ | awk '$1 == PP {print $4}' PP=$$
当 AWK 可以为您完成时,不要运行不必要的应用程序。
假设您/bin/sh
支持 POSIX 标准并且您的系统安装了命令 -在这种情况下lsof
可能是替代方法- 您还可以使用(或调整)以下打印完整路径的脚本:lsof
pid2path
#!/bin/sh
# cat /usr/local/bin/cursh
set -eu
pid="$$"
set -- sh bash zsh ksh ash dash csh tcsh pdksh mksh fish psh rc scsh bournesh wish Wish login
unset echo env sed ps lsof awk getconf
# getconf _POSIX_VERSION # reliable test for availability of POSIX system?
PATH="`PATH=/usr/bin:/bin:/usr/sbin:/sbin getconf PATH`"
[ $? -ne 0 ] && { echo "'getconf PATH' failed"; exit 1; }
export PATH
cmd="lsof"
env -i PATH="${PATH}" type "$cmd" 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; }
awkstr="`echo "$@" | sed 's/\([^ ]\{1,\}\)/|\/\1/g; s/ /$/g' | sed 's/^|//; s/$/$/'`"
ppid="`env -i PATH="${PATH}" ps -p $pid -o ppid=`"
[ "${ppid}"X = ""X ] && { echo "no ppid found"; exit 1; }
lsofstr="`lsof -p $ppid`" ||
{ printf "%s\n" "lsof failed" "try: sudo lsof -p \`ps -p \$\$ -o ppid=\`"; exit 1; }
printf "%s\n" "${lsofstr}" |
LC_ALL=C awk -v var="${awkstr}" '$NF ~ var {print $NF}'
我的解决方案:
ps -o command | grep -v -e "\<ps\>" -e grep -e tail | tail -1
这应该可以跨不同的平台和外壳移植。它ps
与其他解决方案一样使用,但它不依赖sed
或awk
过滤掉管道和ps
自身中的垃圾,因此外壳应该始终是最后一个条目。这样我们就不需要依赖不可移植的 PID 变量或挑选正确的行和列。
我已经在 Debian 和 macOS 上使用 Bash、Z shell ( zsh
) 和fish进行了测试(如果不更改专门针对 fish 的表达式,它不适用于大多数这些解决方案,因为它使用不同的 PID 变量)。
如果您只想检查您是否正在运行(特定版本的)Bash,最好的方法是使用$BASH_VERSINFO
数组变量。作为(只读)数组变量,它不能在环境中设置,因此您可以确定它(如果有的话)来自当前 shell。
但是,由于 Bash 在调用为 时具有不同的行为sh
,因此您还需要检查以 .$BASH
结尾的环境变量/bash
。
在我编写的使用带-
(不是下划线)的函数名称并依赖于关联数组(在 Bash 4 中添加)的脚本中,我进行了以下健全性检查(带有有用的用户错误消息):
case `eval 'echo $BASH@${BASH_VERSINFO[0]}' 2>/dev/null` in
*/bash@[456789])
# Claims bash version 4+, check for func-names and associative arrays
if ! eval "declare -A _ARRAY && func-name() { :; }" 2>/dev/null; then
echo >&2 "bash $BASH_VERSION is not supported (not really bash?)"
exit 1
fi
;;
*/bash@[123])
echo >&2 "bash $BASH_VERSION is not supported (version 4+ required)"
exit 1
;;
*)
echo >&2 "This script requires BASH (version 4+) - not regular sh"
echo >&2 "Re-run as \"bash $CMD\" for proper operation"
exit 1
;;
esac
在第一种情况下,您可以省略对功能的有点偏执的功能检查,并假设未来的 Bash 版本将是兼容的。
没有一个答案适用于fish
shell(它没有变量$$
or $0
)。
这对我有用(在sh
, bash
, fish
, ksh
, csh
, true
, tcsh
, 和zsh
; openSUSE 13.2 上测试):
ps | tail -n 4 | sed -E '2,$d;s/.* (.*)/\1/'
此命令输出一个字符串,如bash
. 这里我只使用ps
, tail
, and sed
(没有 GNU 扩展;尝试添加--posix
以检查它)。它们都是标准的 POSIX 命令。我确定tail
可以移除,但我的sed
fu不够强大,无法做到这一点。
在我看来,这个解决方案不是很便携,因为它在 OS X 上不起作用。:(
echo $$ # Gives the Parent Process ID
ps -ef | grep $$ | awk '{print $8}' # Use the PID to see what the process is.
这不是一个非常干净的解决方案,但它可以满足您的需求。
# MUST BE SOURCED..
getshell() {
local shell="`ps -p $$ | tail -1 | awk '{print $4}'`"
shells_array=(
# It is important that the shells are listed in descending order of their name length.
pdksh
bash dash mksh
zsh ksh
sh
)
local suited=false
for i in ${shells_array[*]}; do
if ! [ -z `printf $shell | grep $i` ] && ! $suited; then
shell=$i
suited=true
fi
done
echo $shell
}
getshell
现在您可以使用$(getshell) --version
.
但是,这仅适用于KornShell类 shell (ksh)。
执行以下操作以了解您的 shell 是否使用 Dash/Bash。
ls –la /bin/sh
:
如果结果是/bin/sh -> /bin/bash
==> 那么你的 shell 正在使用 Bash。
如果结果是/bin/sh ->/bin/dash
==> 那么你的 shell 正在使用 Dash。
如果您想从 Bash 更改为 Dash 或反之亦然,请使用以下代码:
ln -s /bin/bash /bin/sh
(将外壳更改为 Bash)
注意:如果上述命令导致错误提示 /bin/sh 已存在,请删除 /bin/sh 并重试。
我特别喜欢 Nahuel Fouilleul 的解决方案,但我必须在带有内置 Bash shell 的 Ubuntu 18.04 上运行它的以下变体:
bash -c 'shellPID=$$; ps -ocomm= -q $shellPID'
没有临时变量shellPID
,例如:
bash -c 'ps -ocomm= -q $$'
只会ps
为我输出。也许您并不都使用非交互模式,这会有所不同吗?
不需要从“ps”的输出中提取 PID,因为您可以从 /proc 目录结构中读取任何 PID 的相应命令行:
echo $(cat /proc/$$/cmdline)
但是,这可能并不比简单地更好:
echo $0
关于运行与名称所指示的实际不同的 shell,一个想法是使用您之前获得的名称从 shell 请求版本:
<some_shell> --version
sh
退出代码 2 似乎失败了,而其他人提供了一些有用的东西(但我无法验证所有内容,因为我没有它们):
$ sh --version
sh: 0: Illegal option --
echo $?
2
在 Mac OS X(和 FreeBSD)上:
ps -p $$ -axco command | sed -n '$p'
一种方法是:
ps -p $$ -o exe=
这比使用-o args
或-o comm
在另一个答案中建议的 IMO 更好(这些可能使用例如一些符号链接,例如当/bin/sh
指向某些特定外壳时作为破折号或 bash)。
以上返回可执行文件的路径,但请注意,由于 /usr-merge,可能需要检查多个路径(例如/bin/bash
和 /usr/bin/bash
)
另请注意,上述内容不完全兼容 POSIX(POSIX ps 没有exe
)。
为什么不使用$SHELL
var 获取它,一个简单的 sed 可以删除路径:
$ echo $SHELL | sed -E 's/^.*\/([aA-zZ]+$)/\1/g'
bash
在 MacOS、Ubuntu、CentOS 上测试
请使用以下命令:
ps -p $$ | tail -1 | awk '{print $4}'
您可以使用echo $SHELL|sed "s/\/bin\///g"
我想出了这个
sed 's/.*SHELL=//; s/[[:upper:]].*//' /proc/$$/environ
这个在Red Hat Linux (RHEL)、macOS、BSD 和一些AIX上运行良好:
ps -T $$ | awk 'NR==2{print $NF}'
或者,如果pstree可用,以下一个也应该工作,
pstree | egrep $$ | awk 'NR==2{print $NF}'