장기 실행 while 루프를 사용하여 셸 스크립트 최적화

장기 실행 while 루프를 사용하여 셸 스크립트 최적화

나는 다음을 수행해야 하는 쉘 스크립트를 작성했습니다.

  1. 세션 명령을 파일로 캡처합니다.
  2. 각 개별 명령은 별도의 파일에 저장됩니다.
  3. 특정 기준에 따라 각 개별 명령 파일의 내용을 메일로 보냅니다.

내가 관찰한 바에 따르면 루프는 최소 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줄을 출력할 수 있습니까?

내 스크립트 조각은 완전히 테스트되지 않았으며 예상대로 작동하지 않을 수 있습니다. 그러나 나는 원본 스크립트 발췌의 의미를 따르려고 노력했습니다.

관련 정보