Bash 检测路径中是否有特定的目录项

Bash 检测路径中是否有特定的目录项,bash,shell,path,Bash,Shell,Path,使用/bin/bash,如何检测用户的$PATH变量中是否有特定目录 比如说 if [ -p "$HOME/bin" ]; then echo "Your path is missing ~/bin, you might want to add it." else echo "Your path is correctly set" fi 一些非常简单和天真的事情: echo "$PATH"|grep -q whatever && echo "found it" 你要寻

使用/bin/bash,如何检测用户的$PATH变量中是否有特定目录

比如说

if [ -p "$HOME/bin" ]; then
  echo "Your path is missing ~/bin, you might want to add it."
else
  echo "Your path is correctly set"
fi

一些非常简单和天真的事情:

echo "$PATH"|grep -q whatever && echo "found it"
你要寻找的是什么。而不是&&您可以将$?或者使用适当的if语句

限制包括:

上面的内容将匹配较大路径的子字符串。尝试在bin上进行匹配,它可能会找到它,尽管bin不在您的路径中,/bin和/usr/bin都在 上述操作不会自动展开快捷方式,如~ 或者使用perl单行程序:

perl -e 'exit(!(grep(m{^/usr/bin$},split(":", $ENV{PATH}))) > 0)' && echo "found it"
这仍然有一个限制,即它不会执行任何shell扩展,但如果子字符串匹配,它不会失败。以上匹配/usr/bin,以防不清楚。

使用grep是过分的,如果您正在搜索任何包含re元字符的内容,则可能会导致问题。使用bash的内置[[命令:


请注意,在扩展$PATH和要搜索的路径之前添加冒号可以解决子字符串匹配问题;双引号引用路径可以避免使用元字符的问题。

下面介绍如何在没有grep的情况下执行此操作:

这里的关键是使用?构造使冒号和通配符成为可选的。此表单中的元字符应该没有任何问题,但如果您想包含引号,则它们就是这样的:

if [[ "$PATH" == ?(*:)"$HOME/bin"?(:*) ]]
这是使用match操作符=~的另一种方法,因此语法更像grep的:

$PATH是一个字符串列表,以:分隔,用于描述目录列表。目录是一个字符串列表,以/分隔。两个不同的字符串可能指向同一目录,如$HOME和~,或/usr/local/bin和/usr/local/bin/。因此,我们必须修正要比较/检查的规则。我建议比较/检查整个字符串,以d不是物理目录,而是删除重复的和尾随的/

首先从$PATH中删除重复项和尾随项/项:


我编写了以下shell函数来报告当前路径中是否列出了目录。此函数与POSIX兼容,将在Dash和Bash等兼容shell中运行,而不依赖于Bash特定的功能

它包括将相对路径转换为绝对路径的功能。它为此使用readlink或realpath实用程序,但如果提供的目录没有..或其他链接作为其路径的组件,则不需要这些工具。除此之外,该函数不需要外壳外部的任何程序

# Check that the specified directory exists – and is in the PATH.
is_dir_in_path()
{
  if  [ -z "${1:-}" ]; then
    printf "The path to a directory must be provided as an argument.\n" >&2
    return 1
  fi

  # Check that the specified path is a directory that exists.
  if ! [ -d "$1" ]; then
    printf "Error: ‘%s’ is not a directory.\n" "$1" >&2
    return 1
  fi

  # Use absolute path for the directory if a relative path was specified.
  if command -v readlink >/dev/null ; then
    dir="$(readlink -f "$1")"
  elif command -v realpath >/dev/null ; then
    dir="$(realpath "$1")"
  else
    case "$1" in
      /*)
        # The path of the provided directory is already absolute.
        dir="$1"
      ;;
      *)
        # Prepend the path of the current directory.
        dir="$PWD/$1"
      ;;
    esac
    printf "Warning: neither ‘readlink’ nor ‘realpath’ are available.\n"
    printf "Ensure that the specified directory does not contain ‘..’ in its path.\n"
  fi

  # Check that dir is in the user’s PATH.
  case ":$PATH:" in
    *:"$dir":*)
      printf "‘%s’ is in the PATH.\n" "$dir"
      return 0
      ;;
    *)
      printf "‘%s’ is not in the PATH.\n" "$dir"
      return 1
      ;;
  esac
}

使用:$PATH的部分:确保模式也匹配所需的路径是否是路径中的第一个或最后一个条目。这个巧妙的技巧基于此。

完全没有必要为此使用像grep这样的外部实用程序。下面是我一直在使用的,它应该可以移植回Bourne shell的旧版本

case :$PATH: # notice colons around the value
  in *:$HOME/bin:*) ;; # do nothing, it's there
     *) echo "$HOME/bin not in $PATH" >&2;;
esac

这是一种蛮力方法,但除了路径条目包含冒号外,它在所有情况下都有效。并且除了shell之外,不使用任何程序

previous_IFS=$IFS
dir_in_path='no'
export IFS=":"
for p in $PATH
do
  [ "$p" = "/path/to/check" ] && dir_in_path='yes'
done

[ "$dir_in_path" = "no" ] && export PATH="$PATH:/path/to/check"
export IFS=$previous_IFS

这里是一个纯bash实现,它不会由于部分匹配而检测到误报

if [[ $PATH =~ ^/usr/sbin:|:/usr/sbin:|:/usr/sbin$ ]] ; then
  do stuff
fi
这里发生了什么?从3.0版开始,=~操作符使用bash中提供的正则表达式模式支持。正在检查三种模式,由正则表达式或操作符|分隔

所有三个子模式都相对相似,但它们的差异对于避免部分匹配很重要


在正则表达式中,^匹配到行首,$匹配到行尾。如前所述,第一个模式只有在其查找的路径是$path中的第一个值时才会计算为true。第三个模式只有在其查找的路径是$path中的最后一个值时才会计算为true。当它会在其他值之间找到它正在寻找的路径,因为它正在寻找$path变量使用的分隔符,:,到正在搜索的路径的任一侧。

匹配行首/行尾和冒号(如JasonWoof的答案中所示)会考虑子字符串匹配限制。波浪扩展不是常见问题,因为大多数情况下人们允许在分配时进行扩展,例如PATH=~/bin:$PATH,但如果需要,可以使用替换。我正在使用用冒号包装图案的grep解决方案。您的答案启发了我:if[$readlink-f/etc/mtab | grep'\/proc\/'&echo找到了它];然后echo做其他事情echo做不同的事情正如您所提到的那样,fi是if的正确用法吗?:*是一种需要在大多数平台shopt-s extglob上单独显式启用的模式。无需在[[和]]内引用.Word拆分和路径名扩展不会在那里发生。@bobbogo引号需要放在=的右侧,以防目录包含任何glob元字符,这可能是不寻常的,但也是可能的。它对左侧没有影响,但我更喜欢将varia双引号
除非有特别的理由不去做,否则到处都是乱七八糟的东西,而不是试图跟踪所有关于在哪里安全或不安全的奇怪规则,例如,在这里比较的右边。@Gordondavison你是对的!在==等的RHS扩展中出现的任何glob模式都被解释为这样。天哪,你每天都能从Stack中学到一些东西。Tvms是否应将已接受的答案更改为@Gordon Davidson提供的基于bash内置的高投票率答案?如果您有可从sh、ksh、bash或zsh获取的文件,则此方法非常有用 echo $PATH | tr -s / | sed 's/\/:/:/g;s/:/\n/g' | grep -q "^$d$" || echo "missing $d"
# Check that the specified directory exists – and is in the PATH.
is_dir_in_path()
{
  if  [ -z "${1:-}" ]; then
    printf "The path to a directory must be provided as an argument.\n" >&2
    return 1
  fi

  # Check that the specified path is a directory that exists.
  if ! [ -d "$1" ]; then
    printf "Error: ‘%s’ is not a directory.\n" "$1" >&2
    return 1
  fi

  # Use absolute path for the directory if a relative path was specified.
  if command -v readlink >/dev/null ; then
    dir="$(readlink -f "$1")"
  elif command -v realpath >/dev/null ; then
    dir="$(realpath "$1")"
  else
    case "$1" in
      /*)
        # The path of the provided directory is already absolute.
        dir="$1"
      ;;
      *)
        # Prepend the path of the current directory.
        dir="$PWD/$1"
      ;;
    esac
    printf "Warning: neither ‘readlink’ nor ‘realpath’ are available.\n"
    printf "Ensure that the specified directory does not contain ‘..’ in its path.\n"
  fi

  # Check that dir is in the user’s PATH.
  case ":$PATH:" in
    *:"$dir":*)
      printf "‘%s’ is in the PATH.\n" "$dir"
      return 0
      ;;
    *)
      printf "‘%s’ is not in the PATH.\n" "$dir"
      return 1
      ;;
  esac
}
case :$PATH: # notice colons around the value
  in *:$HOME/bin:*) ;; # do nothing, it's there
     *) echo "$HOME/bin not in $PATH" >&2;;
esac
previous_IFS=$IFS
dir_in_path='no'
export IFS=":"
for p in $PATH
do
  [ "$p" = "/path/to/check" ] && dir_in_path='yes'
done

[ "$dir_in_path" = "no" ] && export PATH="$PATH:/path/to/check"
export IFS=$previous_IFS
if [[ $PATH =~ ^/usr/sbin:|:/usr/sbin:|:/usr/sbin$ ]] ; then
  do stuff
fi