如何确定我所在的当前交互式shell(命令行)

如何确定我正在处理的当前shell?

ps命令的输出就足够了吗?

这如何在不同风格的Unix中完成?

612969 次浏览
  • 有三种方法可以找到当前shell可执行文件的姓名

    请注意,如果shell的可执行文件是/bin/sh,这三种方法都可能被愚弄,但它实际上是一个重命名的bash,例如(经常发生)。

    因此,你的第二个问题ps输出是否可以用“不总是”来回答。

    1. echo $0-将打印程序名称…在shell的情况下是实际的shell。

    2. ps -ef | grep $$ | grep -v grep-这将在正在运行的进程列表中查找当前进程ID。由于当前进程是shell,因此将包含它。

      这不是100%可靠的,因为您可能有其他进程,其ps列表包含与shell的进程ID相同的数字,特别是如果该ID是一个小数字(例如,如果shell的PID为“5”,您可能会在相同的grep输出中找到名为“java5”或“perl5”的进程!)。

    3. echo $SHELL-任何shell的当前shell路径都存储为SHELL变量。需要注意的是,如果您作为子进程显式启动shell(例如,它不是您的登录shell),您将获得登录shell的值。如果有这种可能性,请使用ps$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上安装的shksh之间的整个环境变量集的唯一差异是$ERRNO$FCEDIT$LINENO$PS40、$PS3$PS4$PS43、$PS44和$PS45。

你可以试试:

ps | grep `echo $$` | awk '{ print $4 }'

或:

echo $SHELL

ps是最可靠的方法。不保证设置SHell环境变量,即使设置了,也很容易被欺骗。

echo $$ # Gives the Parent Process ID
ps -ef | grep $$ | awk '{print $8}' # Use the PID to see what the process is.

来自你怎么知道你当前的shell是什么?

ps -p $$

应该适用于涉及ps -efgrep的解决方案所做的任何地方(在任何支持ps的POSIX选项的Unix变体上),并且不会因greping可能出现在其他地方的数字序列而引入误报。

在Mac OS X(和FreeBSD)上:

ps -p $$ -axco command | sed -n '$p'

试试看

ps -p $$ -oargs=

ps -p $$ -ocomm=

$SHELL不需要总是显示当前shell。它只反映要调用的默认shell。

要测试上述内容,假设bash是默认shell,请尝试echo $SHELL,然后在同一个终端中,进入其他shell(例如KornShell(ksh))并尝试$SHELL。在这两种情况下,您都将看到结果为bash。

要获取当前shell的名称,请使用cat /proc/$$/cmdline。以及readlink /proc/$$/exe的shell可执行文件的路径。

如果您的/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}'

如果您只想确保用户正在使用Bash调用脚本:

if [ -z "$BASH" ] ;then echo "Please run this script $0 with bash"; exit 1; fi

请使用以下命令:

ps -p $$ | tail -1 | awk '{print $4}'

下面将始终给出使用的实际shell-它获取实际可执行文件的名称而不是shell名称(即ksh93而不是ksh等)。对于/bin/sh,它将显示使用的实际shell,即dash

ls -l /proc/$$/exe | sed 's%.*/%%'

我知道有很多人说ls输出永远不应该被处理,但是你使用的shell以特殊字符命名或放置在以特殊字符命名的目录中的概率是多少?如果情况仍然如此,还有很多其他不同的例子。

正如Toby Speight所指出的,这将是实现同样目标的更恰当和更清洁的方式:

basename $(readlink /proc/$$/exe)

如果您只想检查您是否正在运行(特定版本的)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(它没有变量$$$0)。

这适用于我(在shbashfishkshcshtruetcshzsh上测试;openSUSE 13.2):

ps | tail -n 4 | sed -E '2,$d;s/.* (.*)/\1/'

该命令输出一个类似于bash的字符串。这里我只使用pstailsed(没有GNU删除;尝试添加--posix来检查它)。它们都是标准的POSIX命令。我确信tail可以被删除,但是我的sed fu不够强大,无法做到这一点。

在我看来,这个解决方案不是很便携,因为它在OS X上不起作用。:(

我尝试过许多不同的方法,对我来说最好的方法是:

ps -p $$

它也可以在Cygwin下工作,并且不能像PID greping一样产生误报。经过一些清理,它只输出一个可执行名称(在带有路径的Cygwin下):

ps -p $$ | tail -1 | awk '{print $NF}'

你可以创建一个函数,这样你就不必记住它:

# Print currently active shell
shell () {
ps -p $$ | tail -1 | awk '{print $NF}'
}

…然后执行shell

它在Debian和Cygwin下进行了测试。

不需要从“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

这不是一个非常干净的解决方案,但它做了你想要的。

# 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)。

我在打印父进程时的变体:

ps -p $$ | awk '$1 == PP {print $4}' PP=$$

当AWK可以为您执行时,不要运行不必要的应用程序。

我有一个简单的技巧来找到当前的shell。只需输入一个随机字符串(这不是命令)。它将失败并返回“未找到”错误,但在该行开始时它会说它是哪个shell:

ksh: aaaaa: not found [No such file or directory]
bash: aaaaa: command not found

我的解决方案:

ps -o command | grep -v -e "\<ps\>" -e grep -e tail | tail -1

这应该可以跨不同的平台和shell移植。它像其他解决方案一样使用ps,但它不依赖于sedawk,并过滤掉管道和ps本身的垃圾,因此shell应该始终是最后一个条目。这样我们就不需要依赖不可移植的PID变量或挑选正确的行和列。

我已经在Debian和macOS上使用Bash、z shellzsh)和进行了测试(如果不更改专门针对fish的表达式,大多数解决方案都不起作用,因为它使用了不同的PID变量)。

有很多方法可以找到shell及其对应的版本。这里有一些对我有用的方法。

直截了当

  1. $>回声0美元(给你程序名称。在我的例子中,输出是-bash。)
  2. $>$壳(这会将您带入shell,并在提示符中获得shell名称和版本。在我的情况下bash3.2$。)
  3. $>cho$shell(这将为您提供可执行路径。在我的情况下/bin/bash。)
  4. $>$shell--version(这将提供有关许可证类型的shell软件的完整信息)

粗俗的方法

$> ******* (键入一组随机字符,在输出中您将获得shell名称。

这个在红帽Linux(RHEL)、macOS、BSD和一些AIXes上运行良好:

ps -T $$ | awk 'NR==2{print $NF}'

或者,如果pstree可用,下面的一个也应该工作,

pstree | egrep $$ | awk 'NR==2{print $NF}'

执行以下操作以了解您的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(将shell更改为Bash)

说明:如果上面的命令导致错误, /bin/sh已经存在,删除 /bin/sh然后重试。

于是我想到了这个:

sed 's/.*SHELL=//; s/[[:upper:]].*//' /proc/$$/environ

我特别喜欢Nahuel Fouilleul解,但我必须在ubuntu 18.04(Bionic Beaver)上使用内置的Bash shell运行以下变体:

bash -c 'shellPID=$$; ps -ocomm= -q $shellPID'

如果没有临时变量shellPID,例如:

bash -c 'ps -ocomm= -q $$'

请为我输出ps。也许你们并不都使用非交互模式,这会有所不同。

使用$SHELL环境变量获取它。简单的ed可以删除路径:

echo $SHELL | sed -E 's/^.*\/([aA-zZ]+$)/\1/g'

输出:

bash

它在macOSubuntuCentOS上进行了测试。

一种方法是:

ps -p $$ -o exe=

在我看来,这比使用-o args-o comm更好,正如另一个答案所建议的那样(这些可能使用,例如,一些符号链接,例如当/bin/sh指向某个特定的shell作为DashBash时)。

上面返回可执行文件的路径,但请注意,由于 /usr-merge,可能需要检查多个路径(例如,/bin/bash/usr/bin/bash)。

另请注意,上述内容并不完全与POSIX兼容(POSIXps没有exe)。

您可以使用echo $SHELL|sed "s/\/bin\///g"