나는 다음을 수행해야 하는 쉘 스크립트를 작성했습니다.
- 세션 명령을 파일로 캡처합니다.
- 각 개별 명령은 별도의 파일에 저장됩니다.
- 특정 기준에 따라 각 개별 명령 파일의 내용을 메일로 보냅니다.
내가 관찰한 바에 따르면 루프는 최소 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
누구든지 논리를 나누거나 변경하거나 함수 또는 다른 것을 사용하여 이 코드를 최적화하는 방법을 도와줄 수 있습니까?
여기서는 쉘 스크립트만 사용해야 하며 스크립트를 실행하는 서버는 이를 처리하는 데 3GB 이상의 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을 호출하는 두 for 루프에서만 발생한다고는 생각하지 않습니다. ${SCRIPT_LOC}/running_status.txt
스크립트가 한 시간 정도 실행되면 처음 10줄을 출력할 수 있습니까?
내 스크립트 조각은 완전히 테스트되지 않았으며 예상대로 작동하지 않을 수 있습니다. 그러나 나는 원본 스크립트 발췌의 의미를 따르려고 노력했습니다.