管道、移位或参数扩展哪个更有效?

管道、移位或参数扩展哪个更有效?

我试图找到最有效的方法来迭代某些值,这些值在空格分隔的单词列表中彼此远离的值数量一致(我不想使用数组)。例如,

list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"

所以我希望能够迭代列表并仅访问 1、5、6、9 和 15。

编辑:我应该明确表示,我试图从列表中获取的值的格式不必与列表的其余部分不同。它们的特殊之处仅在于它们在列表中的位置(在本例中为位置 1、4、7...)。所以列表可能是,1 2 3 5 9 8 6 90 84 9 3 2 15 75 55但我仍然想要相同的数字。而且,假设我不知道列表的长度,我希望能够做到这一点。

目前我想到的方法有:

方法一

set $list
found=false
find=9
count=1
while [ $count -lt $# ]; do
    if [ "${@:count:1}" -eq $find ]; then
    found=true
    break
    fi
    count=`expr $count + 3`
done

方法二

set list
found=false
find=9
while [ $# ne 0 ]; do
    if [ $1 -eq $find ]; then
    found=true
    break
    fi
    shift 3
done

方法三 我很确定管道使这是最糟糕的选择,但出于好奇,我试图找到一种不使用 set 的方法。

found=false
find=9
count=1
num=`echo $list | cut -d ' ' -f$count`
while [ -n "$num" ]; do
    if [ $num -eq $find ]; then
    found=true
    break
    fi
    count=`expr $count + 3`
    num=`echo $list | cut -d ' ' -f$count`
done

那么什么是最有效的,或者我错过了一个更简单的方法?

答案1

  • 软件优化第一法则:

    在您知道程序的速度是一个问题之前,无需考虑它有多快。如果您的列表大约有这个长度或只有大约 100-1000 个项目,您可能甚至不会注意到它需要多长时间。您可能会花更多时间考虑优化而不是差异。

  • 第二条规则:措施

    这是找出答案的可靠方法,也是为您的系统提供答案的方法。尤其是贝壳,种类繁多,而且并不完全相同。针对某一 shell 的答案可能不适用于您的 shell。

    在较大的程序中,分析也在这里进行。最慢的部分可能不是您想象的那样。

  • 三、shell脚本优化第一条规则:不要使用外壳

    是的,真的。许多 shell 的设计速度并不快(因为启动外部程序不必如此),它们甚至可能每次都会再次解析源代码行。

    请改用 awk 或 Perl 之类的东西。在我所做的一个简单的微基准测试中,awk运行简单循环(没有 I/O)的速度比任何常见 shell 快几十倍。

    但是,如果您确实使用 shell,请使用 shell 的内置函数而不是外部命令。在这里,您使用的expr不是我在系统上找到的任何 shell 中内置的,但可以用标准算术扩展替换。例如,i=$((i+1))而不是i=$(expr $i + 1)增加i。您在上一个示例中使用的cut也可以替换为标准参数扩展。

    也可以看看:为什么使用 shell 循环处理文本被认为是不好的做法?

步骤 #1 和 #2 应该适用于您的问题。

答案2

非常简单awk。这将为您提供任意长度输入的每四个字段的值:

$ awk -F' ' '{for( i=1;i<=NF;i+=3) { printf( "%s%s", $i, OFS ) }; printf( "\n" ) }' <<< $list
1 5 6 9 15

这可以利用内置awk变量(例如NF(记录中的字段数量)),并执行一些简单的for循环来迭代字段,从而为您提供所需的字段,而无需提前知道会有多少个字段。

或者,如果您确实只想要示例中指定的那些特定字段:

$ awk -F' ' '{ print $1, $4, $7, $10, $13 }' <<< $list
1 5 6 9 15

至于效率问题,最简单的方法是测试这个方法或其他每个方法,并用它time来显示需要多长时间;您还可以使用诸如strace查看系统调用流程之类的工具。的用法time看起来像:

$ time ./script.sh

real    0m0.025s
user    0m0.004s
sys     0m0.008s

您可以比较不同方法之间的输出,看看哪种方法在时间方面最有效;其他工具可用于其他效率指标。

答案3

我只会在这个答案中提供一些一般性建议,而不是基准。基准是可靠地回答有关性能问题的唯一方法。但既然你不说多少您正在操作的数据和多常当您执行此操作时,无法进行有用的基准测试。 10 个项目的效率更高和 1000000 个项目的效率通常是不一样的。

作为一般经验法则,只要纯 shell 代码不涉及循环,调用外部命令就比使用纯 shell 构造执行某些操作更昂贵。另一方面,迭代大字符串或大量字符串的 shell 循环可能比调用专用工具慢。例如,您的循环调用cut在实践中可能会明显很慢,但如果您找到一种方法通过一次调用完成整个任务,cut则可能比在 shell 中使用字符串操作执行相同的操作更快。

请注意,系统之间的截止点可能会有很大差异。它可能取决于内核、内核调度程序的配置方式、包含外部可执行文件的文件系统、当前的 CPU 与内存压力以及许多其他因素。

expr如果您完全关心性能,请不要调用来执行算术。事实上,expr根本不调用来执行算术。 Shell 有内置的算术,比调用更清晰、更快expr

您似乎正在使用 bash,因为您使用的是 sh 中不存在的 bash 结构。那么到底为什么不使用数组呢?数组是最自然的解决方案,而且也可能是最快的。请注意,数组索引从 0 开始。

list=(1 2 3 5 9 8 6 90 84 9 3 2 15 75 55)
for ((count = 0; count += 3; count < ${#list[@]})); do
  echo "${list[$count]}"
done

sh如果您使用 sh,如果您的系统使用 dash 或 ksh as而不是 bash,您的脚本可能会更快。如果您使用 sh,则不会获得命名数组,但您仍然可以获得位置参数的数组之一,您可以使用 进行设置set。要访问直到运行时才知道的位置的元素,您需要使用eval(注意正确引用内容!)。

# List elements must not contain whitespace or ?*\[
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
set $list
count=1
while [ $count -le $# ]; do
  eval "value=\${$count}"
  echo "$value"
  count=$((count+1))
done

如果您只想访问数组一次并且从左到右(跳过某些值),则可以使用shift变量索引来代替。

# List elements must not contain whitespace or ?*\[
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
set $list
while [ $# -ge 1 ]; do
  echo "$1"
  shift && shift && shift
done

哪种方法更快取决于 shell 和元素的数量。

另一种可能性是使用字符串处理。它的优点是不使用位置参数,因此您可以将它们用于其他用途。对于大量数据来说,速度会更慢,但对于少量数据来说,这不太可能产生明显的差异。

# List elements must be separated by a single space (not arbitrary whitespace)
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
while [ -n "$list" ]; do
  echo "${list% *}"
  case "$list" in *\ *\ *\ *) :;; *) break;; esac
  list="${list#* * * }"
done

答案4

也许是这个?

cut -d' ' -f1,4,7,10,13 <<<$list
1 5 6 9 15

相关内容