【问题标题】:Check if a variable exists in a list in Bash检查 Bash 列表中是否存在变量
【发布时间】:2011-12-25 04:21:11
【问题描述】:

我正在尝试用 bash 编写一个脚本来检查用户输入的有效性。
我想将输入(比如变量x)与有效值列表匹配。

我现在想到的是:

for item in $list
do
    if [ "$x" == "$item" ]; then
        echo "In the list"
        exit
    fi
done

我的问题是是否有更简单的方法来做到这一点,
对于大多数编程语言,类似于 list.contains(x)

说清单是:

list="11 22 33"

我的代码将仅针对这些值回显消息,因为 list 被视为数组而不是字符串, 所有字符串操作都将验证1,而我希望它失败。

【问题讨论】:

    标签: linux bash


    【解决方案1】:
    [[ $list =~ (^|[[:space:]])$x($|[[:space:]]) ]] && echo 'yes' || echo 'no'
    

    或者创建一个函数:

    contains() {
        [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && exit(0) || exit(1)
    }
    

    使用它:

    contains aList anItem
    echo $? # 0: match, 1: failed
    

    【讨论】:

    • 应该是[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
    • 如果用户输入包含正则表达式特殊字符,例如x=.,可能会误报
    • 这不会给出误报/否定:contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }
    • 更简洁的解决方案:[[ " $list " =~ " $x " ]] && echo 'yes' || echo 'no'。假设空格是分隔符并且$x 不包含空格是正确的
    • 我认为最好使用“is in”函数isIn(),这样您就可以先在参数中写入项目。你也可以echo 一些东西而不是像这样使用exit[[ $2 =~ (^|[[:space:]])$1($|[[:space:]]) ]] && echo 1 || echo 0 所以你可以这样使用函数:result=$(isIn "-t" "-o -t 45") && echo $result
    【解决方案2】:

    怎么样

    echo $list | grep -w -q $x
    

    您可以检查输出或以上行的$? 来做出决定。

    grep -w 检查整​​个单词的模式。添加-q 可防止回显列表。

    【讨论】:

    • 如果“value”是有效的(即在列表中),这不会验证“val”,因为它是它的子字符串
    • 是的,就像接受的答案一样。 @glennjackman 提供了一个可行的解决方案。
    • 你可以使用 grep -q 让 grep 安静
    • @carnicer 然后只需使用 grep -w $x 进行完全匹配
    • 这需要 grep,但考虑到这个工具几乎可以在任何现代 Linux/Unix 系统上使用,它是迄今为止更简单易读的解决方案。
    【解决方案3】:

    Matvey 是对的,但你应该引用 $x 并考虑任何类型的“空格”(例如新行)

    [[ $list =~ (^|[[:space:]])"$x"($|[[:space:]]) ]] && echo 'yes' || echo 'no' 
    

    所以,即

    # list_include_item "10 11 12" "2"
    function list_include_item {
      local list="$1"
      local item="$2"
      if [[ $list =~ (^|[[:space:]])"$item"($|[[:space:]]) ]] ; then
        # yes, list include item
        result=0
      else
        result=1
      fi
      return $result
    }
    

    然后结束

    `list_include_item "10 11 12" "12"`  && echo "yes" || echo "no"
    

    if `list_include_item "10 11 12" "1"` ; then
      echo "yes"
    else 
      echo "no"
    fi
    

    请注意,如果是变量,您必须使用""

    `list_include_item "$my_list" "$my_item"`  && echo "yes" || echo "no"
    

    【讨论】:

    • 即使$item 包含特殊字符,如.,此解决方案也有效(但$list 变量可能需要在测试中引用)。并且函数可以定义得更简单:contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
    • 在以下情况下不起作用:list="aa bb xx cc ff"x="aa bb"
    • 你好。我正在尝试学习 bashscript 基础知识,我想知道如何将列表字符串解析为列表,然后在该列表中进行存在检查。我可以理解您使用空格进行拆分,但我无法完全理解该表达式。你能给我谷歌这个表达式的基本关键字吗:$list =~ (^|[[:space:]])"$item"($|[[:space:]])。或者,如果您有时间,我会很高兴听到您对此表达的解释。注意:我猜这是一个正则表达式(以 ^ 等开头),但我不知道 =~ 是什么意思。所以我想要一个全面的解释:P
    【解决方案4】:

    恕我直言,最简单的解决方案是在原始字符串前面加上一个空格,然后用[[ ]]检查正则表达式

    haystack='foo bar'
    needle='bar'
    
    if [[ " $haystack " =~ .*\ $needle\ .* ]]; then
        ...
    fi
    

    对于包含 needle 作为子字符串的值,这不会是误报,例如大海捞针foo barbaz

    (这个概念无耻地从JQuery的hasClass()-Method中窃取)

    【讨论】:

    • 如果分隔符不是空格,解决方法更明显。也可以不使用正则表达式:haystack="foo:bar"[[ ":$haystack:" = *:$needle:* ]]
    • 如果您使用 =~ 并引用指针,则不需要任何通配符(无论分隔符如何):[[ " $haystack " =~ " $needle " ]](请参阅this answer to a similar question)。
    【解决方案5】:

    如果使用双括号,也可以在 case 语句之外使用 (* 通配符):

    string='My string';
    
    if [[ $string == *My* ]]
    then
    echo "It's there!";
    fi
    

    【讨论】:

    • 如果“My”是另一个字符串的子字符串,这将给出误报,例如string='MyCommand 字符串'。
    • 值得注意的是,通配符 (*My*) 必须在测试的右侧。
    【解决方案6】:

    如果要在脚本中硬编码您的值列表,则使用case 进行测试相当简单。这是一个简短的示例,您可以根据自己的要求进行调整:

    for item in $list
    do
        case "$x" in
          item1|item2)
            echo "In the list"
            ;;
          not_an_item)
            echo "Error" >&2
            exit 1
            ;;
        esac
    done
    

    如果列表在运行时是一个数组变量,那么其他答案之一可能更合适。

    【讨论】:

      【解决方案7】:

      如果不是太长;您可以像这样在逻辑或比较之间将它们串起来。

      if [ $ITEM == "item1" -o $ITEM == "item2" -o $ITEM == "item3" ]; then
          echo In the list
      fi 
      

      我遇到了这个确切的问题,虽然上面的问题很难看,但比其他通用解决方案更明显。

      【讨论】:

        【解决方案8】:

        如果列表在脚本中是固定的,我最喜欢以下:

        validate() {
            grep -F -q -x "$1" <<EOF
        item 1
        item 2
        item 3
        EOF
        }
        

        然后使用validate "$x"来测试$x是否被允许。

        如果你想要一个单行,并且不关心项目名称中的空格,你可以使用这个(注意 -w 而不是 -x):

        validate() { echo "11 22 33" | grep -F -q -w "$1"; }
        

        注意事项:

        • 这是 POSIX sh 兼容的。
        • validate 接受子字符串(如果需要,请将 -x 选项删除到 grep)。
        • validate 将其参数解释为固定字符串,而不是常规字符串 表达式(如果需要,请将 -F 选项删除到 grep)。

        执行功能的示例代码:

        for x in "item 1" "item2" "item 3" "3" "*"; do
            echo -n "'$x' is "
            validate "$x" && echo "valid" || echo "invalid"
        done
        

        【讨论】:

          【解决方案9】:

          考虑利用associative arrays 的密钥。我认为这优于正则表达式/模式匹配和循环,尽管我没有对其进行分析。

          declare -A list=( [one]=1 [two]=two [three]='any non-empty value' )
          for value in one two three four
          do
              echo -n "$value is "
              # a missing key expands to the null string, 
              # and we've set each interesting key to a non-empty value
              [[ -z "${list[$value]}" ]] && echo -n '*not* '
              echo "a member of ( ${!list[*]} )"
          done
          

          输出:

          one is a member of ( one two three )
          two is a member of ( one two three )
          three is a member of ( one two three )
          four is *not* a member of ( one two three )
          

          【讨论】:

          • ...您可以通过创造性地使用参数来简化替换(而不是依赖于echo -n):do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done
          • 如果我只有一个(长)列表,例如 `list="one two three x y z ...",是否有一种简单的方法来创建这样的数组?
          【解决方案10】:

          我发现使用echo $LIST | xargs -n1 echo | grep $VALUE 形式更容易,如下图所示:

          LIST="ITEM1 ITEM2"
          VALUE="ITEM1"
          if [ -n "`echo $LIST | xargs -n1 echo | grep -e \"^$VALUE`$\" ]; then
              ...
          fi
          

          这适用于以空格分隔的列表,但您可以通过执行以下操作使其适应任何其他分隔符(如 :):

          LIST="ITEM1:ITEM2"
          VALUE="ITEM1"
          if [ -n "`echo $LIST | sed 's|:|\\n|g' | grep -e \"^$VALUE`$\"`" ]; then
             ...
          fi
          

          请注意," 是测试正常工作所必需的。

          【讨论】:

          • 这将导致LIST="SOMEITEM1 ITEM2" 返回true,即使ITEM1 不在其中
          • 很好,我用 grep -e 更新了示例以排除部分匹配。
          • 我相信“if”语句的末尾有一个额外的`。正确的形式是: [ -n "`echo $LIST | xargs -n1 echo | grep -e \"^$VALUE$\" ]
          【解决方案11】:

          我想我会将我的解决方案添加到列表中。

          # Checks if element "$1" is in array "$2"
          # @NOTE:
          #   Be sure that array is passed in the form:
          #       "${ARR[@]}"
          elementIn () {
              # shopt -s nocasematch # Can be useful to disable case-matching
              local e
              for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
              return 1
          }
          
          # Usage:
          list=(11 22 33)
          item=22
          
          if elementIn "$item" "${list[@]}"; then
              echo TRUE;
          else
              echo FALSE
          fi
          # TRUE
          
          item=44
          elementIn $item "${list[@]}" && echo TRUE || echo FALSE
          # FALSE
          

          【讨论】:

            【解决方案12】:

            shell 内置 compgen 可以在这里提供帮助。它可以获取带有 -W 标志的列表并返回它找到的任何潜在匹配项。

            # My list can contain spaces so I want to set the internal
            # file separator to newline to preserve the original strings.
            IFS=$'\n'
            
            # Create a list of acceptable strings.
            accept=( 'foo' 'bar' 'foo bar' )
            
            # The string we will check
            word='foo'
            
            # compgen will return a list of possible matches of the 
            # variable 'word' with the best match being first.
            compgen -W "${accept[*]}" "$word"
            
            # Returns:
            # foo
            # foo bar
            

            我们可以编写一个函数来测试一个字符串是否等于可接受字符串的最佳匹配。这允许您分别返回 0 或 1 表示通过或失败。

            function validate {
              local IFS=$'\n'
              local accept=( 'foo' 'bar' 'foo bar' )
              if [ "$1" == "$(compgen -W "${accept[*]}" "$1" | head -1)" ] ; then
                return 0
              else
                return 1
              fi
            }
            

            现在您可以编写非常干净的测试来验证字符串是否可以接受。

            validate "blah" || echo unacceptable
            
            if validate "foo" ; then
              echo acceptable
            else 
              echo unacceptable
            fi
            

            【讨论】:

              【解决方案13】:

              之前的答案不使用tr,我发现它对grep 很有用。假设列表中的项目以空格分隔,以检查是否完全匹配:

              echo $mylist | tr ' ' '\n' | grep -F -x -q "$myitem"
              

              如果项目在列表中,则返回退出代码 0,否则返回退出代码 1。

              最好作为函数使用:

              _contains () {  # Check if space-separated list $1 contains line $2
                echo "$1" | tr ' ' '\n' | grep -F -x -q "$2"
              }
              
              mylist="aa bb cc"
              
              # Positive check
              if _contains "${mylist}" "${myitem}"; then
                echo "in list"
              fi
              
              # Negative check
              if ! _contains "${mylist}" "${myitem}"; then
                echo "not in list"
              fi
              

              【讨论】:

                【解决方案14】:

                示例

                $ in_list super test me out
                NO
                
                $ in_list "super dude" test me out
                NO
                
                $ in_list "super dude" test me "super dude"
                YES
                
                # How to use in another script
                if [ $(in_list $1 OPTION1 OPTION2) == "NO" ]
                then
                  echo "UNKNOWN type for param 1: Should be OPTION1 or OPTION2"
                  exit;
                fi
                

                in_list

                function show_help()
                {
                  IT=$(CAT <<EOF
                
                  usage: SEARCH_FOR {ITEM1} {ITEM2} {ITEM3} ...
                
                  e.g. 
                
                  a b c d                    -> NO
                  a b a d                    -> YES
                  "test me" how "test me"    -> YES
                
                  )
                  echo "$IT"
                  exit
                }
                
                if [ "$1" == "help" ]
                then
                  show_help
                fi
                
                if [ "$#" -eq 0 ]; then
                  show_help
                fi
                
                SEARCH_FOR=$1
                shift;
                
                for ITEM in "$@"
                do
                  if [ "$SEARCH_FOR" == "$ITEM" ]
                  then
                    echo "YES"
                    exit;
                  fi
                done
                
                echo "NO"
                

                【讨论】:

                  【解决方案15】:

                  假设 TARGET 变量只能是“二项式”或“回归”,那么以下就可以了:

                  # Check for modeling types known to this script
                  if [ $( echo "${TARGET}" | egrep -c "^(binomial|regression)$" ) -eq 0 ]; then
                      echo "This scoring program can only handle 'binomial' and 'regression' methods now." >&2
                      usage
                  fi
                  

                  您可以将更多字符串添加到列表中,方法是用 | 分隔它们。 (管道)字符。

                  使用 egrep 的优势在于,您可以轻松添加不区分大小写 (-i),或使用正则表达式检查更复杂的场景。

                  【讨论】:

                    【解决方案16】:

                    这几乎是您最初的提案,但几乎是 1 行。不像其他有效答案那么复杂,并且取决于 bash 版本(可以与旧 bash 一起使用)。

                    OK=0 ; MP_FLAVOURS="vanilla lemon hazelnut straciatella"
                    for FLAV in $MP_FLAVOURS ; do [ $FLAV == $FLAVOR ] && { OK=1 ; break; } ; done
                    [ $OK -eq 0 ] && { echo "$FLAVOR not a valid value ($MP_FLAVOURS)" ; exit 1 ; }
                    

                    我想我的提案在长度和风格上仍然可以改进。

                    【讨论】:

                      【解决方案17】:

                      受接受的响应启发的替代解决方案,但使用反转逻辑:

                      MODE="${1}"
                      
                      echo "<${MODE}>"
                      [[ "${MODE}" =~ ^(preview|live|both)$ ]] && echo "OK" || echo "Uh?"
                      

                      这里,输入 ($MODE) 必须是正则表达式中的选项之一('preview'、'live' 或 'both'),这与将整个选项列表与用户输入匹配相反。当然,你不希望正则表达式改变。

                      【讨论】:

                        【解决方案18】:

                        演出迟到了?以下非常简单的变体尚未明确提及。我使用case 来检查简单列表,这是一个通用的 Bourne Shell 习惯用法,不依赖任何外部或扩展:

                        haystack='a b c'
                        needle='b'
                        
                        case " $haystack " in (*" $needle "*) :;; (*) false;; esac
                        
                        • 请注意使用分隔符(此处为:SPC)来正确分隔模式:" $haystack " 的开头和结尾以及" $needle " 的测试中。
                        • 如果$needle$haystack 中,则此语句返回true ($?=0),否则返回false。
                        • 您还可以非常轻松地测试多个$needle。当有几个类似的情况像
                          if (haystack.contains(needle1)) { run1() } elif (haystack.contains(needle2)) { run2() } else { run3() }
                          您也可以将其包装到 case 中:
                          case " $haystack " in (*" $needle1 "*) run1;; (*" $needle2 "*) run2;; (*) run3;; esac
                          等等

                        这也适用于值不包括分隔符本身的所有列表,例如逗号:

                        haystack=' a , b , c '
                        needle=' b '
                        
                        case ",$haystack," in (*",$needle,"*) :;; (*) false;; esac
                        

                        请注意,如果值可以包含任何内容,包括分隔符序列(NUL 除外,因为 shell 不支持变量中的 NUL,因为您无法将包含 NUL 的参数传递给命令),那么您需要使用数组。数组是 ksh/bashisms,“普通”POSIX/Bourne shell 不支持。 (您可以在 POSIX-Shell 中使用 $@ 来解决此限制,但这与此处所采用的完全不同。)

                        (*) false 部分可以省略吗?

                        • 不,因为这是关键的返回值。默认情况下case 返回true
                        • 是的,如果您不需要返回值并将您的处理放在: 的位置

                        为什么:;;

                        • 我们也可以写成true;;,但我习惯使用:而不是true,因为它更短更快速输入
                        • 另外,我认为不要写任何不好的做法,因为case 的默认返回值对每个人来说并不明显。
                        • 此外,“省略”该命令通常表示“这里忘记了一些东西”。因此,在此处添加多余的“:”清楚地表明“它除了在此处返回 true 之外什么都不做”。

                        bash 中,您还可以使用ksh/bashisms,如;&amp; (fallthroug) 或;;&amp; (测试其他模式) 来表达if (haystack.contains(needle1)) { run1(); }; if (haystack.contains(needle2)) { run2(); }

                        因此通常case 比其他正则表达式结构更易于维护。而且它不使用正则表达式,它只使用 shell 模式,这可能会更快。


                        可复用功能:

                        : Needle "list" Seperator_opt
                        NeedleListSep()
                        {
                          if [ 3 -gt $# ]; 
                          then NeedleListSep "$1" "$2" " ";
                          else case "$3$2$3" in (*"$3$1$3"*) return 0;; esac; return 1;
                          fi;
                        }
                        

                        bash 中,您可以将其简化为

                        : Needle "list" Seperator_opt
                        NeedleListSep()
                        {
                          local s="${3-" "}";
                          case "$s$2$s" in (*"$s$1$s"*) return 0;; esac; return 1;
                        }
                        

                        这样使用

                        Test() {
                        NeedleListSep "$1" "a b c"           && echo found $1 || echo no $1;
                        NeedleListSep "$1" "a,b,c"     ','   && echo found $1 || echo no $1;
                        NeedleListSep "$1" "a # b # c" ' # ' && echo found $1 || echo no $1;
                        NeedleListSep "$1" "abc"       ''    && echo found $1 || echo no $1;
                        }
                        Test a
                        Test z
                        

                        如上所示,这也适用于分隔符为空字符串的退化情况(因此列表的每个字符都是针)。示例:

                        Test
                        

                        返回

                        no
                        no
                        no
                        found
                        

                        因为空字符串是 abc 的明确部分,以防您的分隔符是空字符串,对吧?

                        请注意,此功能是公共领域,因为它绝对没有任何东西可以真正受版权保护。

                        【讨论】:

                          猜你喜欢
                          • 2011-12-19
                          • 1970-01-01
                          • 2018-04-27
                          • 2015-10-06
                          • 1970-01-01
                          • 2014-09-08
                          • 2020-10-21
                          • 2017-02-13
                          相关资源
                          最近更新 更多