私は以下を行う必要があるシェルスクリプトを書いています。
- セッションコマンドをファイルとしてキャプチャします。
- 各個別コマンドは別々のファイルに保存されます。
- 特定の基準に従って、個々のコマンドファイルの内容をメールで送信します。
私が観察したところ、ループは少なくとも25,000回繰り返す必要があります。今私の問題は、すべての繰り返しを完了するのに6時間以上かかることです。
以下は、処理に長い時間がかかるスクリプトの主要部分です。
if [ -s "$LOC/check.txt" ]; then
while read line; do
echo -e " started processing $line at `date` " >> "$SCRIPT_LOC/running_status.txt"
TST=`grep -w $line $PERM_LOC/id_processing.txt`
USER=`echo $TST | grep -w $line | awk -F '"' '{print $10}'`
HOST=`echo $TST | grep -w $line | awk -F '"' '{print $18}'`
ID=`echo $TST | echo $line | tr -d '\"'`
IP=`echo $TST | grep -w $line | awk -F '"' '{print $20}'`
DB=`echo $TST | grep -w $line | awk -F '"' '{print $22}'`
CONN_TSMP=`echo $TST | grep -w $line | awk -F '"' '{print $2}'`
if [ -z "$IP" ]; then
IP=`echo "$HOST"`
fi
if [ "$USER" == "root" ] && [ -z $DB ]; then
TARGET=/data1/sessions/root_sec
CMD_TARGET=/data1/commands/root_commands
FILE=`echo "$ID-$CONN_TSMP-$USER@$IP.txt"`
else
TARGET=/data1/sessions/user_sec
CMD_TARGET=/data1/commands/user_commands
FILE=`echo "$ID-$CONN_TSMP-$USER@$IP.txt"`
fi
ls $TARGET/$FILE
If [ $? -ne 0 ]; then
echo $TST | awk -F 'STATUS="0"' '{print $2}'| sed "s/[</>]//g" >> "$TARGET/$FILE"
echo -e "\n" >> "$TARGET/$FILE"
fi
grep $line $LOC/out.txt > "$LOC/temp.txt"
while read val; do
TSMP=`echo "$val" | awk -F '"' '{print $2}'`
QUERY=`echo "$val" | awk -F 'SQLTEXT=' '{print $2}' | sed "s/[/]//g"`
echo " TIMESTAMP=$TSMP " >> "$TARGET/$FILE"
echo " QUERY=$QUERY " >> "$TARGET/$FILE"
RES=`echo "$QUERY" | awk {'print $1'} | sed 's/["]//g' `
TEXT=`grep "$RES" "$PERM_LOC/commands.txt"`
if [ -n "$TEXT" ]; then
NUM=`expr $NUM + 1`
SUB_FILE=`echo "$ID-$command-$NUM-$TSMP-$USER@$IP.txt"`
echo -e "===============\n" > "$CMD_TARGET/$SUB_FILE"
echo "FILE = \"$SUB_FILE\"" >> "$CMD_TARGET/$SUB_FILE"
### same way append 6 more lines to $SUB_FILE
SUB=`echo "$WARN_ME" | grep "$command"`
if [ "$command" == "$VC" ]; then
STATE=`echo " very critical "`
elif [ -z "$SUB" ]; then
STATE=CRITICAL
else
STATE=WARNING
fi
if [ "$USER" != "root" -a "$command" != "$VC" ]; then
mail command &
elif [ "$USER" == "root" -a -z "$HOST" ]; then
mail command &
elif [ "$USER" == "root" -a "$command" == "$VC" ]; then
mail command &
else
echo -e "some message \n" >> $LOC/operations.txt
fi
fi
done < "$LOC/temp.txt"
done < "$LOC/check.txt"
fi
誰もがロジックを分割、変更したり、関数や他のものを使用してこのコードを最適化する方法を助けることができますか?
ここではシェルスクリプトのみを使用する必要があり、スクリプトを実行しているサーバーはそれを処理するために3 GB以上のRAMを占有しないでください。
どんな助けでも非常に役立ちます。
答え1
マブソサ!
実行に時間がかかる理由を理解しています。情報をキャッシュしてコンピュータをほとんど殺すのではなく、操作を繰り返しています。貧しいコンピュータ。 :(
awkは軽くないので、同じデータに対して複数回呼び出します。一度実行すると、5つの変数をすべて設定できます。
これが何をすべきか、何を成し遂げるべきかを知らないままできることが多すぎます。
すべての処理がgrep、awk、sed、およびtrであることを考慮すると、このスクリプトをPERLで作成すると、印象的なスピードアップが得られます。 PERLは、テキストとレポートを処理するように設計されています。他のプログラムを繰り返し呼び出すことなく、内部的にこれらのすべてのgrep / awk / sed / tr操作を実行できます。
しかし、いくつかの改善点は次のとおりです。
if [ -s "$LOC/check.txt" ]; then
function setvars() {
CONN_TSMP="$1"
USER="$2"
HOST="$3"
DB="$4"
IP="$5"
return
}
while read line; do
echo " started processing ${line} at $(date) " >> "${SCRIPT_LOC}/running_status.txt"
ID=$(echo "$line" | tr -d '"')
# are you sure you don't want the FIRST match? This will give ALL the matches,
# which will prevent you from getting good values for the variables
# to only get first entry that matches:
# TST=$(grep --max-count=1 -w "$line" "$PERM_LOC/id_processing.txt")
# (or -m 1, but long options document what you're doing better)
TST=$(grep -w "$line" "$PERM_LOC/id_processing.txt")
VARS=$(echo "${TST}" | awk -F '"' '{print "\""$2"\" \""$10"\" \""$18"\" \""$20"\" \""$22'})
# CONN_TSMP USER HOST IP DB
# magic! setvars receives the 5 values awk pulled out (ran it once!)
# NO QUOTES on next line, already has them embedded from awk
setvars $VARS
if [ -z "$IP" ]; then
IP="$HOST"
fi
CMD_TARGET="/data1/commands/user_commands"
FILE="${ID}-${CONN_TSMP}-${USER}@${IP}.txt"
if [ "$USER" == "root" ] && [ -z "$DB" ]; then
TARGET="/data1/sessions/root_sec"
else
TARGET="/data1/sessions/user_sec"
fi
# does this need to be redirected to a file?
ls "$TARGET/$FILE"
if [ $? -ne 0 ]; then
# awk can likely do the print and the removal of </> characters in
# one pass (my awk-fu is weak this morning)
echo "$TST" | awk -F 'STATUS="0"' '{print $2}'| sed "s/[</>]//g" >> "$TARGET/$FILE"
echo -e "\n" >> "$TARGET/$FILE"
fi
# ALWAYS quote your values, embedded spaces will bite you!
grep "$line" "$LOC/out.txt" > "$LOC/temp.txt"
while read val; do
TSMP=$(echo "$val" | awk -F '"' '{print $2}')
QUERY=$(echo "$val" | awk -F 'SQLTEXT=' '{print $2}' | sed "s/[\"/]//g")
echo " TIMESTAMP=$TSMP " >> "$TARGET/$FILE"
echo " QUERY=$QUERY " >> "$TARGET/$FILE"
TEXT=$(grep "$QUERY" "$PERM_LOC/commands.txt")
if [ -n "$TEXT" ]; then
NUM=$(expr $NUM + 1)
# could also be: NUM=$(($NUM+1)) (bash v4.0+)
SUB_FILE="$ID-$command-$NUM-$TSMP-$USER@$IP.txt"
echo -e "===============\n" > "$CMD_TARGET/$SUB_FILE"
echo "FILE = \"$SUB_FILE\"" >> "$CMD_TARGET/$SUB_FILE"
### same way append 6 more lines to $SUB_FILE
SUB=$(echo "$WARN_ME" | grep "$command")
if [ "$command" == "$VC" ]; then
STATE=" very critical "
elif [ -z "$SUB" ]; then
STATE=" CRITICAL "
else
STATE=" WARNING "
fi
if [ "$USER" != "root" -a "$command" != "$VC" ]; then
# this should probably be $command instead of command?
# oh wait, probably a placeholder statement
mail command &
elif [ "$USER" == "root" -a -z "$HOST" ]; then
mail command &
elif [ "$USER" == "root" -a "$command" == "$VC" ]; then
mail command &
else
echo -e "some message \n" >> $LOC/operations.txt
fi
fi
done < "$LOC/temp.txt"
done < "$LOC/check.txt"
fi
まあ、「シェルスクリプトのみ」です。さて、これを念頭に置いて、「$LOC/check.txt」および/または「$LOC/temp.txt」をあらかじめgrepして、ループの代わりに「grep」出力を使用できます。 。
もっと見るほど、awkは一度にデータを処理することでこれを行うことができ、最初の項目だけでなく各項目を処理できるという確信が高まりました(コメントで指摘したように、別のループが必要です)。 「read line」と「read var」ループの間)。
これは長いawkスクリプトになりますが、確かに実行可能です。そしてawkは時間をかけて学ぶ価値があり、それほど難しくなく、ただ違うだけです。マブソサ!
答え2
投稿したコードが機能しないため、重要な情報がたくさんありません。追加情報は、実際に入力と希望の出力が正確に何であるかを説明しません。
それにもかかわらず、パフォーマンスの問題を正しくデバッグできるように、スクリプトからすべてのsed呼び出しとawk呼び出しを削除し、スクリプトを大幅に簡素化する方法は次のとおりです。
#!/usr/bin/env bash
# Should work using bash 3.2+ and the unrevealed part of your code
if [ ! -s "$LOC/check.txt" ]; then
echo "Bummer!"
exit 1
fi
function write_ts () {
echo "[$(date)]: Started processing ${line}" >> ${SCRIPT_LOC}/running_status.txt
}
function set_and_init_file_targets () {
if [ "$USER" == "root" ] && [ -z $DB ]; then
TARGET=/data1/sessions/root_sec
CMD_TARGET=/data1/commands/root_commands
else
TARGET=/data1/sessions/user_sec
CMD_TARGET=/data1/commands/user_commands
fi
FILE="${CONNECTION_ID}-${TIMESTAMP}-${USER}@${IP}.txt"
if [ ! -e "$TARGET/$FILE" ]; then
echo "${_res##*STATUS=0}" > "$TARGET/$FILE"
fi
}
function parse_line () {
local line=$@
while read val; do
res2=${val//[<>\(\)]/}
eval ${res2//AUDIT_RECORD/}
SQLTEXT=${SQLTEXT/%?/}
echo "TIMESTAMP=$TIMESTAMP" >> "$TARGET/$FILE"
echo "QUERY=$SQLTEXT" >> "$TARGET/$FILE"
/* grep the sql command by itself */
TEXT=$(grep -i "${SQLTEXT%% *}" "$PERM_LOC/commands.txt")
if [ -n "$TEXT" ]; then
NUM=$((NUM + 1))
SUB_FILE="$CONNECTION_ID-$command-$NUM-$TIMESTAMP-$USER@$IP.txt"
echo -e "===============\n" > "$CMD_TARGET/$SUB_FILE"
echo "FILE = \"$SUB_FILE\"" >> "$CMD_TARGET/$SUB_FILE"
# [... the rest does not make sense at all ...]
fi
done < <(grep "$line" "$LOC/out.txt")
}
# Main code
while read line; do
# grep line without quotes
TST=$(grep -w "${line//\"/}" "$PERM_LOC/id_processing.txt")
# remove everything besides key=val pairs
res=${TST//[<>\(\)]/}
# set the key=val pairs, except AUDIT_RECORD
eval ${res//AUDIT_RECORD/}
# set IP to HOST if empty
: ${IP:="$HOST"}
# remove nasty / at the end
DB=${DB/%?/}
set_and_init_file_targets
parse_line "$line"
done < "$LOC/check.txt"
この記事を投稿した後は、パフォーマンスの問題がawk / sed / grepを呼び出す2つのforループでのみ発生するとは思いません。${SCRIPT_LOC}/running_status.txt
スクリプトが1時間ほど実行されると、最初の10行を出力できますか?
私のスクリプトスニペットは完全にテストされておらず、期待どおりに機能しない可能性があります。しかし、私は元のスクリプトの抜粋の意味に従おうとしました。