使用内置函数检查 shell 变量是否有多于一行?

Wil*_*ard 6 bash string shell-script posix

我可以通过调用外部实用程序sed(对于已知的非空$myvar)如下所示:

if [ "$(printf %s "$myvar" | sed -n '$=')" -eq 1 ]; then
  echo "Your variable has only one line, proceeding"
else
  echo "Error condition, variable must have only one line"
fi
Run Code Online (Sandbox Code Playgroud)

有没有办法只使用bash内置函数来做到这一点?

更好的是,有没有办法以 POSIX 指定的方式执行此操作,而无需调用外部实用程序?


(这个问题是好奇心和寻找更好的做事方法之一;上面的代码确实起作用。我想知道是否有更干净/更快的方法。)

cuo*_*glm 10

POSIX方式:

NL='
'
case $myvar in
  *"$NL"*) echo more than one line ;;
        *) echo one line ;;
esac
Run Code Online (Sandbox Code Playgroud)

这也适用于 POSIX 之前的类似 Bourne 的 shell。


Luc*_*cas 6

以下片段适用于bash(有和没有-posix选项):

#!/bin/bash
#!/bin/bash -posix
version_1 () { [[ "$myvar" = *$'\n'* ]]; }
version_2 () {
  local newlines="${myvar//[^$'\n']/}"
  [[ "${#newlines}" -eq 1 ]]
}
for test in version_1 version_2; do
  if $test; then echo many lines; else echo one line; fi
done
Run Code Online (Sandbox Code Playgroud)


小智 5

有几个选项(首先是 bash,下面是 POSIX)。
每个函数内部的代码可以很容易地在外部使用。

#!/bin/bash

nl=$'\n'

aregex   (){ [[ $a =~        $nl     ]]; }
apattern (){ [[ $a ==       *$nl*    ]]; }
acut     (){ [[ $a != "${a%%"$nl"*}" ]]; }
areplace (){ [[ $a != "${a//"$nl"/}" ]]; }
acase    (){ case $a in (*$nl*) true;; (*) false;; esac; }
aifs     ()( IFS="$nl"
             set -f; set -- x${a}x;
             (( $# > 1 ));
           )
aread    (){ IFS="$nl" read -rd '' -a b <<<"x${a}x";
             (( "${#b[@]}" > 1 )); }
Run Code Online (Sandbox Code Playgroud)

每个功能都是一个选项。如果只有一行,每个函数将退出,返回值为 0,如果变量$a有多个换行符(多于一个$'\n'),则返回值为 1 。

执行函数后,这将打印所需的行:

out=''; "$function" && out="more than "
printf "%9s   = %sone line\n" "$1" "$out"
Run Code Online (Sandbox Code Playgroud)

执行所有选项:

a='ab'"$nl"'cd' ; alltests
a='ab  cd'      ; alltests
Run Code Online (Sandbox Code Playgroud)

给出这个输出:

   aregex   = more than one line
 apattern   = more than one line
     acut   = more than one line
 areplace   = more than one line
    acase   = more than one line
     aifs   = more than one line
    aread   = more than one line

   aregex   = one line
 apattern   = one line
     acut   = one line
 areplace   = one line
    acase   = one line
     aifs   = one line
    aread   = one line  
Run Code Online (Sandbox Code Playgroud)

POSIX

由于以下几个原因,以下选项在 POSIX 中失败:

  • aregex :=~POSIX 中没有正则表达式运算符。
  • apattern :==POSIX 中没有运算符。
  • areplace : 参数扩展没有${ / / }选项。

四个可以安全地转换为 POSIX:

  • posixcut :可能是最好的解决方案。
  • posixcase :POSIX shell 的通用解决方案。
  • posixifs :在子外壳内执行以能够设置set -f.
  • posixread : read 没有-d-a但可以改编。

#!/bin/dash

nl='
'

posixcut (){ [ "${a}" != "${a%%"$nl"*}" ] ; }
posixcase(){ case $a in (*$nl*) true;; (*) false;; esac; }
posixifs ()( IFS="$nl";
             set -f; set -- x${a}x;
             [ "$#" -gt 1 ];
           )
posixread(){ local b=0;
             while IFS=$nl read -r _; do
                 b=$((b+1));
                 [ $b -gt 1 ] && break;
             done <<-_EOT_
x${a}x
_EOT_
             [ $b -gt 1 ];
           }
Run Code Online (Sandbox Code Playgroud)

注意:是的,local严格来说不是 POSIX,但得到了很好的支持
本地 forb可以安全地删除(检查全局$b使用情况)。

伯恩(1977 年壳)。

如果您需要原始 1977 Bourne shell 的代码,请使用以下代码:

posixcase() { case $a in *$nl*) true;; *) false;; esac; }
posixifs () ( IFS="$nl"
              set -f; set -- x${a}x;
              [ "$#" -gt 1 ];
            )
bourneread()( b=0                ### A helper function.
              while :; do
                  if IFS=$nl read c && [ $b -le 1 ]; then
                      b=`expr $b + 1`
                  else
                      echo "$b"
                      break
                  fi    
              done <<-_EOT_
x${a}x
_EOT_
             )
posixread   (){ [ `bourneread` -gt 1 ]; }
Run Code Online (Sandbox Code Playgroud)

${a%% }posixcut 中使用的扩展在 Bourne 中不起作用。posixifs 需要分为两部分才能在 Bourne 中工作。
读取选项需要市长更改,但可以正常工作。
它的函数是posixread,bourneread是一个必需的辅助函数。

Bourne 的所有代码都经过测试,可以与传家宝 shell一起正常工作。