【问题标题】:Bash manipulate and sort file content with arrays via loopBash 通过循环使用数组对文件内容进行操作和排序
【发布时间】:2019-06-27 18:42:56
【问题描述】:

目的

创建一个循环执行某些命令的 bash 脚本,并将每个命令的输出 (它们只打印数字) 保存到一个文件中 (我想最好的方法是将它们保存在文件?)在每个输出旁边都有日期(unix时间),这样我们下次运行脚本时就可以使用这些存储的值,它会再次循环,看看是否没有过去一小时内命令输出的任何变化。

示例输出

# ./script
command1 123123
command2 123123

重要提示

  • 脚本将循环执行大约 200 个命令。
  • 将来会有新的命令,所以脚本必须检查这个命令是否存在于保存的文件中。如果它已经存在,请仅在最后一小时内比较它,以查看自上次保存文件以来该数字是否已更改。如果不存在,将其保存到文件中,以便我们下次使用它进行比较。
  • 随着命令的增加/减少/更改,脚本将运行的命令顺序可能会有所不同。所以,如果现在只是这样;
# ./script
command1 123123
command2 123123

如果你以后添加第三条命令,顺序可能会改变(也不确定它遵循什么样的模式),例如;

# ./script
command1 123123
command3 123123
command2 123123

例如,我们不能逐行阅读,在这种情况下,我认为最好的方法是将它们与command* 名称进行比较。

存储值的结构

我假定的存储值结构是这样的(不必坚持这个);

command1 123123 unixtime
command2 123123 unixtime

关于上述命令

我称为commands 的东西基本上是在/usr/local/bin/ 上运行的应用程序,可以通过直接在shell 上运行它们的名称来访问,例如command1 getnumber,它会打印出你的号码。

由于命令位于/usr/local/bin/ 中并遵循类似的模式,因此我首先循环遍历/usr/local/bin/ 以获取command*。见下文。

commands=`find /usr/local/bin/ -name 'command*'`

for i in $commands; do
    echo "$i" "`$i getnumber`"
done

所以这将遍历所有以command 开头的文件并为每个文件运行command* getnumber,这将打印出我们需要的数字。

现在我们需要将这些值存储在一个文件中,以便下次运行命令时进行比较。

赶上:

我们甚至可以每隔几分钟运行一次脚本,但我们只需要报告值(数字)在过去一小时内没有变化

该脚本将在您每次运行时列出 数字,我们可能会为过去一小时内未更改的那些添加样式以使其突出显示,可能就像添加一个对他们来说是红色的?

尝试次数 #1

所以这是我第一次尝试构建这个脚本。这是它的样子;

#!/bin/bash

commands=`find /usr/local/bin/ -name 'command*'`
date=`date +%s`

while read -r command number unixtime; do
    for i in $commands; do
        current_block_count=`$i getnumber`
        if [[ $command = $i ]]; then
            echo "$i exists in the file, checking the number changes within last hour" # just for debugging, will be removed in production
            if (( ($date-$unixtime)/60000 > 60 )); then
                if (( $number >= $current_number_count )); then
                    echo "There isn't a change within the last hour, this is a problem!" # just for debugging, will be removed in production
                    echo -e "$i" "`$i getnumber`" "/" "$number" "\e[31m< No change within last hour."
                else
                    echo "$i" "`$i getnumber`"
                    echo "There's a change within the last hour, we're good." # just for debugging, will be removed in production
                    # find the line number of $i so we can change it with the new output
                    line_number=`grep -Fn '$i' outputs.log`
                    new_output=`$i getnumber`
                    sed -i "$line_numbers/.*/$new_output/" outputs.log
                fi
            else
                echo "$i" "`$i getnumber`"
                # find the line number of $i so we can change it with the new output
                line_number=`grep -Fn '$i' outputs.log`
                output_check="$i getnumber; date +%s"
                new_output=`eval ${output_check}`
                sed -i "$line_numbers/.*/$new_output/" outputs.log
            fi
        else
            echo "$i does not exists in the file, adding it now" # just for debugging, will be removed in production
            echo "$i" "`$i getnumber`" "`date +%s`" >> outputs.log
        fi
    done
done < outputs.log

这是一场灾难,最终,当我运行它时,它什么也没做。

尝试次数 #2

这一次,我尝试了另一种方法,将 for loop 嵌套在 while loop 之外。

#!/bin/bash

commands=`find /usr/local/bin/ -name 'command*'`
date=`date +%s`


for i in $commands; do
    echo "${i}" "`$i getnumber`"
    name=${i}
    number=`$i getnumber`
    unixtime=$date
    echo "$name" "$number" "$unixtime" # just for debugging, will be removed in production
    while read -r command number unixtime; do
        if ! [ -z ${name+x} ]; then
            echo "$name" "$number" "$unix" >> outputs.log
        else
            if [[ $name = $i ]]; then
                if (( ($date-$unixtime)/60000 > 60 )); then
                    if (( $number >= $current_number_count )); then
                        echo "There isn't a change within the last hour, this is a problem!" # just for debugging, will be removed in production
                        echo -e "$i" "`$i getnumber`" "/" "$number" "\e[31m< No change within last hour."
                    else
                        echo "$i" "`$i getnumber`"
                        echo "There's a change within the last hour, we're good." # just for debugging, will be removed in production
                        # find the line number of $i so we can change it with the new output
                        line_number=`grep -Fn '$i' outputs.log`
                        new_output=`$i getnumber`
                        sed -i "$line_numbers/.*/$new_output/" outputs.log
                    fi
                else
                    echo "$i" "`$i getnumber`"
                    # find the line number of $i so we can change it with the new output
                    line_number=`grep -Fn '$i' outputs.log`
                    output_check="$i getnumber; date +%s"
                    new_output=`eval ${output_check}`
                    sed -i "$line_numbers/.*/$new_output/" outputs.log
                fi
            else
                echo "$i does not exists in the file, adding it now" # just for debugging, will be removed in production
                echo "$i" "`$i getnumber`" "`date +%s`" >> outputs.log
            fi
        fi
    done < outputs.log
done

不幸的是,我又没有运气了。

有人可以帮帮我吗?

补充说明 #2

所以基本上,你第一次运行脚本,outputs.log 是空的,所以你将命令的输出写入outputs.log

10 分钟过去了,你再次运行脚本,因为它只过去了 10 分钟,不超过一个小时,脚本不会检查数字是否发生了变化。它不会操纵存储的值,而且每次运行它时都会向我们显示命令的输出。 (他们目前的输出,而不是来自存储的值)

例如,在这 10 分钟的时间范围内,可能已经添加了新命令,因此它会在您每次运行脚本时检查是否存储了命令的输出,以处理新命令。

现在已经过去了,假设 1.2 小时过去了,您决定再次运行脚本,这次脚本将检查一个多小时后数字是否没有变化,并报告我们说Hey! It's been more than an hour passed and those numbers still haven't changed, there might be problem!

简单说明

  • 您有 100 条命令要运行,您的脚本将遍历每个命令并为每个命令执行以下操作;
  • 随时运行脚本
  • 每次运行时,检查outputs.log 是否包含命令
    • 如果outputs.log 包含每个循环的命令,请检查每个循环的最后存储日期($unixtime)。
      • 如果上次存储的日期超过一个小时,请检查当前运行和存储值之间的数字
        • 如果数字超过一个小时没有变化,请运行红色文本命令。
        • 如果数字已更改,请照常运行命令,而不会发出任何警告。
      • 如果上次存储的日期少于一小时,请照常运行命令。
    • 如果outputs.log 不包含该命令,只需将它们存储在文件中,以便下次运行检查时使用。

【问题讨论】:

  • 您是否只关心最近的输出是否与上次运行命令时不同,或者是否与过去一小时内的任何先前运行不同?
  • 您有 output.log 的初始版本吗?您可以将“set -x”放在脚本的前面以跟踪正在执行的内容,这可能会有所帮助。
  • 看来我忘记提了。我没有日志的初始版本,我希望让脚本自己创建一个。
  • 基本上我希望它不是每次运行时都存储这些值,但如果只有最后保存的值超过 1 小时。
  • 我觉得我的问题不太清楚,所以我添加了一些额外的注释和更简单的解释。各位大佬能查一下吗?

标签: arrays bash for-loop while-loop


【解决方案1】:

以下使用 sqlite 数据库来存储结果,而不是平面文件,这使得查询以前运行的历史记录变得容易:

#!/bin/sh

database=tracker.db

if [ ! -e "$database" ]; then
    sqlite3 -batch "$database" <<EOF
CREATE TABLE IF NOT EXISTS outputs(command TEXT
                                 , output INTEGER
                                 , ts INTEGER NOT NULL DEFAULT (strftime('%s', 'now')));
CREATE INDEX IF NOT EXISTS outputs_idx ON outputs(command, ts);
EOF
fi

for cmd in /usr/local/bin/command*; do
    f=$(basename "$cmd")
    o=$("$cmd")
    echo "$f $o"
    sqlite3 -batch "$database" <<EOF
INSERT INTO outputs(command, output) VALUES ('$f', $o);
SELECT command || ' has unchanged output!'
FROM outputs
WHERE command = '$f' AND ts >= strftime('%s', 'now', '-1 hour')
GROUP BY command
HAVING count(DISTINCT output) = 1 AND count(*) > 1;
EOF
done

它列出了在过去一小时内每次运行都产生相同输出的命令(并跳过只运行一次的命令)。相反,如果您对每个命令的最新输出与该小时时间范围内的上一次运行相同的情况感兴趣,请将循环中的 sqlite3 调用替换为:

sqlite3 -batch $database <<EOF
INSERT INTO outputs(command, output) VALUES ('$f', $o);
WITH prevs AS
 (SELECT command
       , output
       , row_number() OVER w AS rn
       , lead(output, 1) OVER w AS prev
  FROM outputs
  WHERE command = '$f' AND ts >= strftime('%s', 'now', '-1 hour')
  WINDOW w AS (ORDER BY ts DESC))
SELECT command || ' has unchanged output!'
FROM prevs
WHERE output = prev AND rn = 1;
EOF

(这需要 3.25 或更高版本的 sqlite3 shell,因为它使用了当时引入的功能。)

【讨论】:

  • 我不太熟悉使用数据库,但我会试试这个。谢谢。
  • 如何向此脚本运行的命令添加参数?因为命令应该遵循getnumber 参数来执行。如果您只运行命令本身,它们将不会运行。它应该像这样运行; /usr/local/bin/command* getnumber 给我们一个输出。
  • @MarryJane 您只需将任何需要的参数添加到运行命令的表达式中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-15
相关资源
最近更新 更多