Tuesday, November 17, 2009

SW302 - Лабораторын ажил 5

Бид энэ ажлаар ямар нэгэн скрипт бичихгүй, харин бичсэн скриптийг нарийн судалж скрипт бичих аргуудыг сурах болно. Гол маш сайн судалж ойлгох хэрэгтэй. Скрипт ~/sw302/lab05/ufa.sh нэртэйгээр үүсэх ёстой. Шалгах хугацаа:VI

Дараах бодолтыг ажиллуулж туршиж үз. Хамгийн гол нь ойлгож өөрийн болгох хэрэгтэй.

#!/bin/bash


#----functions start------------------------------------------------------------
function usage() {
}


function filter_older_than() {
}


function filter_newer_than() {
}


function filter_date_between() {
}


function filter_bigger_than() {
}


function filter_smaller_than() {
}


function filter_words() {
}


function filter_namefilter() {
}


function filter_keep_total_size_under() {
}


function action_list() {
}


function action_list_detail() {
}


function action_delete() {
}


function action_replace() {
}


function action_newrepository {
}


#----functions end--------------------------------------------------------------



# sanity check
if [ $# = 0 ] ; then usage; fi


# exit status
ec_bad_command=-1
ec_no_match=-2
ec_unexpected_param=-3
ec_failed_command=-4
ec_bad_param=-5


# setup :- defaults
repository=~/sw302/lab05/repository
date_format=%Y%m%d%H%M.%S
date_file_1=.junk1
date_file_2=.junk2


# setup :- variables for filter
filter=$1
filter_param_1=''
filter_param_2=''
filter_param_count=0
filter_matched=''


# setup :- variables for action
action=''
action_param_1=''


# main filter dispatcher
case '${filter}' in
'--help')
    usage
;;
'--older_than')
;;
'--newer_than')
;;
'--date_between')
;;
'--bigger_than')
;;
'--smaller_than')
;;
'--words')
;;
'--namefilter')
;;
'--keep_total_size_under')
;;
*)
    usage
;;
esac

Энэ бол манай скриптийн ерөнхий бүтэц. Бид дараа нь илүү нарийвчилж гүйцээх болно. Ерөнхий санаа нь:

  1. case -г олон функцүүдийн 'диспетчер' маягаар ашиглах -- Лаб #4 -т байсантай ижилхэн.
  2. Шүүлтүүр 'filter' бүрт, мөн үйлдэл 'action' бүрт зориулж функц үүсгэх.
  3. Скриптэд болон зарим функцүүдэд ашиглагдах хувьсагчдыг үүсгэх.

Алхам #2 - Эхлээд амарханыг нь хий

  •     source: ~/sw302/lab05/ufa.sh

function usage() {
    echo -e '\
`basename $0`: Invalid command!

GET HELP:
ufa.sh  [ --help ]

FILTER & ACTION:
ufa.sh  [ [ --older_than | --newer_than ] date | --date_between date1 date2 ]   --|
    [ ( --bigger_than | --smaller_than ) file_size_in_byte ]          |--> FILTER
    [ --words words ... | --namefilter filenames_filters ... ]      --|
    [ --delete ]                                --|
    [ --list ]                                |
    [ --list_detail ]                             |--> ACTION
    [ --replace replace_word ]                        |
    [ --newrepository repository_name_tar.gz ]              --|

TRIM:
ufa.sh  [ --keep_total_size_under total_repository_size_in_bytes ]        |--> TRIM REPOSITORY'

    exit $ec_bad_command
}

Алхам #3 - Шүүлтүүрүүд

  •     source: ~/sw302/lab05/ufa.sh

function filter_older_than() {
    assert_not_null $filter_param_1 $filter
    touch -t $filter_param_1 $date_file_1
    filter_matched=`find $repository \! -newer $date_file_1 -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}


function filter_newer_than() {
    assert_not_null $filter_param_1 $filter
    touch -t $filter_param_1 $date_file_1
    filter_matched=`find $repository -newer $date_file_1 -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}


function filter_date_between() {
    assert_not_null $filter_param_1 $filter
    assert_not_null $filter_param_2 $filter
    touch -t $filter_param_1 $date_file_1
    touch -t $filter_param_2 $date_file_2
    filter_matched=`find $repository -newer $date_file_1 \! -newer $date_file_2 -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}


function filter_bigger_than() {
    assert_not_null $filter_param_1 $filter
    filter_matched=`find $repository -size +${filter_param_1}c -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}


function filter_smaller_than() {
    assert_not_null $filter_param_1 $filter
    filter_matched=`find $repository -size -${filter_param_1}c -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}


function filter_words() {
    assert_not_null $filter_param_1 $filter
    filter_matched=`grep -rlEw $filter_param_1 $repository`
    assert_last_cmd_success $?
    dispatch_action $*
}


function filter_namefilter() {
    assert_not_null $filter_param_1 $filter
    filter_matched=`find $repository -regextype posix-extended -regex '^.*/[^\/]*(${filter_param_1})[^\/]*$' -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}


function filter_keep_total_size_under() {
    assert_not_null $filter_param_1 $filter


    local allfiles=`find $repository -size +1c -type f -printf %h/%f:%s\\\n | sort -rn -t \: -k 2`
    local currentsize=0
    local maxsize=$(($filter_param_1-1))
    local delete_all='false'
    for a in $allfiles ; do
        if [ '${delete_all}' == 'true' ] ; then
            rm -f `echo $a | cut -d \: -f 1` &> /dev/null
            continue
        fi


        currentsize=$(($currentsize+`echo $a | cut -d \: -f 2`))
        if [ $currentsize -gt $maxsize ] ; then
            rm -f `echo $a | cut -d \: -f 1` &> /dev/null
            delete_all='true'
        fi
    done
}


  • Эдгээр функцүүдийн ихэнх нь Лаб #3, Лаб #4 -с хуулагдсан. Гэхдээ заримд нь бага зэргийн өөрчлөлтүүд хэрэгтэй.
  • filter_namefilter() функц нэлээн сонирхолтой. Энэ тохиолдолд  find -ийн анхдагч энгийн илэрхийллийн боловсруулагч нь тохиромжгүй байна. Бидэнд ЗӨВХӨН нэр нь таарсан файлууд л хэрэгтэй учраас 'өргөтгөгдсөн' төрлийн энгийн илэрхийлэл шаардлагатай байна. Дээр байгаа энгийн илэрхийлэл ^.*/[^\/]*(${filter_param_1})[^\/]*$ нь 'эхлэлээс хамгийн сүүлийн '/' хүртэл дурын тэмдэгтийг тааруулaaд (match) , бүтэн замын үлдсэн хэсгийг хэрэглэгчийн оруулсан зүйлийн дагуу тааруул' гэсэн үг юм.
  • Мөн хэдэн нийтлэг функцийг нэмсэн байгаа:

  1. check_date_format() : параметр нь зөв эсэхийг шалгах --  $date_format -ын дагуу байх ёстой.
  2. assert_no_more_param(): өөр ямар нэгэн параметр үлдээгүй эсэхийг шалгах, хэрэв үлдсэн бол мэдээлэл гаргаад програмыг дуусгах.
  3. assert_not_null(): хувьсагч null биш эсэхийг шалгах, үгүй бол алдааны мэдээлэл гаргаад програмыг дуусгах.
  4. assert_last_cmd_success(): сүүлийн команд  алдаагүй ажилласан эсэхийг шалгах (0-с бага биш), үгүй бол алдааны мэдээлэл гаргаад програмыг дуусгах.
  5. dispatch_action(): өөр нэгэн зохицуулагч маягаар ашиглагдах энэ функц нь 'үйлдэл'-д тохирох функцийг дуудах болно. Энэ функц мөн 'үйлдэл'-д шаардлагатай аргументуудыг тодорхойлох болно.

Алхам #4 - Үндсэн 'зохицуулагч'-ийг дуудах

  • source: ~/sw302/lab05/ufa.sh

# main filter dispatcher
case '${filter}' in
'--help')
    usage
;;
'--older_than' | '--newer_than')
    check_date_format $2
    shift 2
;;
'--date_between')
    check_date_format $2
    # we need to retain the first date since check_date_format always
    # write to variable filter_param_1
    tmp=$filter_param_1
    check_date_format $3
    filter_param_2=$filter_param_1
    filter_param_1=$tmp
    shift 3
;;
'--bigger_than' | '--smaller_than')
    filter_param_1=$2
    shift 2
;;
'--words' | '--namefilter')
    shift 1
    fetch_and_shift_args $*
    shift $filter_param_count
;;
'--keep_total_size_under')
    filter_param_1=$2
    shift 2
;;
*)
    usage
;;
esac


# make sure that --keep_total_size_under has no more than 1 parameter.
if [ '${filter}' == '--keep_total_size_under' ] ; then
    assert_no_more_param $*
else
    action=$1
    shift 1
fi


# invoking the filter function.  The function name is the same as removing
# the first two '--' from $filter variable.
result='`filter_${filter##--} $*`'
echo '${result:=''}'

  • Үндсэн 'зохицуулагч'-ийг илүү үр дүнтэй ажилладаг болгож өөрчлөнө. Хэрэв илүү ажиглавал, зарим 'шүүлтүүр' нь хоорондоо төстэй байгаа. Тиймээс бид тэдгээрийг бүлэглэж болно -- жишээ нь ('--older_than' | '--newer_than').
  • Мөн 'шүүлтүүр' функцийг илүү автоматаар дууддаг болгож болно. Жишээ нь: '--older_than' нь 'filter_older_than' функцийг дуудна. Хэрэв бид '--' -г '--older_than' -с хасаад 'filter_' -г урд нь залгаж орхивол  'filter_older_than' буюу дуудах функцийн нэр нь гарч ирнэ -- `filter_${filter##--} ...`
  • Командын мөрийн аргументуудыг shift командыг ашиглан гүйлгэж болдог. Үүнийг ашигласнаар уншихад амархан байдаг. $1 нь $0, $2 нь $1, ... болно.

Алхам #5 - Нийтлэг функцүүд

  •     source: ~/sw302/lab05/ufa.sh

# check that $1 has the correct date format -- must conformed to $date_format
function check_date_format() {
    if [ '${1}' == '' ] ; then
        echo 'Error! Invalid date format : empty'
        exit $?
    fi


    # try change $1 to the proper date format
    filter_param_1=`date -d '${1}' +$date_format 2> /dev/null`
    if [ $? != 0 ] ; then
        echo 'Error! Invalid date format : ${1}'
        exit $?
    fi
}


# make sure that there is no more parameters to parse
function assert_no_more_param() {
    if [ '${*}' != '' ] ; then
        echo 'Unexpected commandline parameters: ${*}'
        exit $ec_unexpected_param
    fi
}


# make sure that the parameter ($1) is not null.
function assert_not_null() {
    if [ '$1' == '' ] ; then
        echo 'A valid parameter is expected for $2'
        exit $ec_bad_param
    fi
}


# make sure that the last command run successfully (i.e. without any exit
# code less than 0)
function assert_last_cmd_success() {
    if [ $1 != 0 ]; then
        echo 'Your filter command ($filter) failed'
        exit $ec_failed_command
    fi
}


# this function determine the parameter(s) needed for a function, as well
# as invoking the function itself.
function dispatch_action() {
    # sanity check
    if [ '${filter_matched}' = '' ] ; then
        echo No files matched
        exit 0
    fi


    # take care of default action
    if [ '${action}' == '' ] ; then
        action='--list'
    fi


    case '${action}' in
    '--delete' | '--list' | '--list_detail')
        assert_no_more_param $*
    ;;
    '--replace' | '--newrepository')
        action_param_1=$1
        shift 1
        assert_no_more_param $*
    ;;
    *)
        usage
    ;;
    esac


    # invoking the function.  The function name is the same as removing
    # the first two '--' from $action variable.
    action_result='`action_${action##--} $*`'
    echo '${action_result:=''}'
}


    * dispatch_action() функц нь 'dispatcher' -тэй төстэй ажиллана.


Алхам #6 - 'үйлдэл' функцүүд


    * source: ~/sw302/lab05/ufa.sh


function action_list() {
    for a in ${filter_matched} ; do
        echo $a
    done
}


function action_list_detail() {
    for a in ${filter_matched} ; do
        find $repository -wholename '$a' -printf %h/%f\\t%s\\t%TY-%Tm-%Td\ %TH:%Tm:%TS\\n
    done
}


function action_delete() {
    for a in $filter_matched ; do
        rm $a &> /dev/null
        assert_last_cmd_success $?
    done
}


function action_replace() {
    # sanity check
    if [ '${filter}' != '--words' ] ; then
        echo '--replace is only valid when used with --words'
        exit $ec_bad_command
    fi


    assert_not_null $action_param_1 $action
    for a in $filter_matched ; do
        sed -ire 's/${filter_param_1}/${action_param_1}/g' $a
        assert_last_cmd_success $?
    done
}


function action_newrepository {
    # first we'll collect all the matched file into $tmp_file
    local tmp_file='.tmp.newrepository.lst'
    for a in $filter_matched ; do
        echo $a >> $tmp_file
    done


    # use $tmp_file to instruct tar command to create tar.gz file
    tar -zc -T $tmp_file 1> $action_param_1 2> /dev/null


    # now we should store the outcome of the previous command, because
    # when we execute the next command (rm -fR ...), the $? value would
    # be changed.
    local tar_ec=$?


    rm -fR $tmp_file &> /dev/null
    assert_last_cmd_success $tar_ec
}


  • Эдгээр функцүүдийн ихэнх нь чамд ойлгомжтой байгаа байх. Гэхдээ би action_newrepository() функцийг тайлбарлая. Эхлээд бүх тохирсон файлуудыг түр зуурын файл ($tmp_file) руу хадгална. Дараа нь бид tar командаар .tar.gz  үүсгэж энэ файлыг илгээнэ. Одоо бид түр зуурын файлаа устгаж орчноо цэвэрлэж болно. Гэхдээ tar командыг амжилттай ажилласан эсэхийг шалгахын тулд эхлээд буцаасан төлвийг хадгалах хэрэгтэй. $? локаль хувьсагч руу хадгалаад түр зуурын файлаа устгаад  assert_last_cmd_success -г дуудна.

Скрипт бүхлээрээ

  •     source: ~/sw302/lab05/ufa.sh

#!/bin/bash


#----functions start------------------------------------------------------------
function usage() {
    echo -e '\
`basename $0`: Invalid command!

GET HELP:
ufa.sh  [ --help ]

FILTER & ACTION:
ufa.sh  [ [ --older_than | --newer_than ] date | --date_between date1 date2 ]   --|
    [ ( --bigger_than | --smaller_than ) file_size_in_byte ]          |--> FILTER
    [ --words words ... | --namefilter filenames_filters ... ]      --|
    [ --delete ]                                --|
    [ --list ]                                |
    [ --list_detail ]                             |--> ACTION
    [ --replace replace_word ]                        |
    [ --newrepository repository_name_tar.gz ]              --|

TRIM:
ufa.sh  [ --keep_total_size_under total_repository_size_in_bytes ]        |--> TRIM REPOSITORY'

    exit $ec_bad_command
}

# check that $1 has the correct date format -- must conformed to $date_format
function check_date_format() {
    if [ '${1}' == '' ] ; then
        echo 'Error! Invalid date format : empty'
        exit $?
    fi

    # try change $1 to the proper date format
    filter_param_1=`date -d '${1}' +$date_format 2> /dev/null`
    if [ $? != 0 ] ; then
        echo 'Error! Invalid date format : ${1}'
        exit $?
    fi
}

# make sure that there is no more parameters to parse
function assert_no_more_param() {
    if [ '${*}' != '' ] ; then
        echo 'Unexpected commandline parameters: ${*}'
        exit $ec_unexpected_param
    fi
}

# make sure that the parameter ($1) is not null.
function assert_not_null() {
    if [ '$1' == '' ] ; then
        echo 'A valid parameter is expected for $2'
        exit $ec_bad_param
    fi
}

# make sure that the last command run successfully (i.e. without any exit
# code less than 0)
function assert_last_cmd_success() {
    if [ $1 != 0 ]; then
        echo 'Your filter command ($filter) failed'
        exit $ec_failed_command
    fi
}

# get all the commandline parameters until we reach a parameter that starts
# with '--'.  The collected parameters are reformatted to the 'arg1|arg2'
# format for regex processing.  In addition, the number of parameters
# collected will be stored in $filter_param_count variable.
# For example, 'a b c d e --f' will cause this function to gather the
# first 5 parameters as 'a|b\c|d|e' and $filter_param_count = 5.
function fetch_and_shift_args() {
    local params=''
    filter_param_count=0
    for a in $* ; do
        # test if $a starts with --
        local b=${a#--}
        if [ '${a}' = '${b}' ] ; then
            # not a command, we'll use this as a parameters
            filter_param_count=$(($filter_param_count+1))

            # concatenate params with the 'arg1|arg2|arg3' format
            if [ '${params}' == '' ] ; then
                params=$a
            else
                params='${params}|$a'
            fi
        else
            # found a command, quit now
            break;
        fi
    done
    filter_param_1='$params'
}

# this function determine the parameter(s) needed for a function, as well
# as invoking the function itself.
function dispatch_action() {
    # sanity check
    if [ '${filter_matched}' = '' ] ; then
        echo No files matched
        exit 0
    fi

    # take care of default action
    if [ '${action}' == '' ] ; then
        action='--list'
    fi

    case '${action}' in
    '--delete' | '--list' | '--list_detail')
        assert_no_more_param $*
    ;;
    '--replace' | '--newrepository')
        action_param_1=$1
        shift 1
        assert_no_more_param $*
    ;;
    *)
        usage
    ;;
    esac

    # invoking the function.  The function name is the same as removing
    # the first two '--' from $action variable.
    action_result='`action_${action##--} $*`'
    echo '${action_result:=''}'
}

function filter_older_than() {
    assert_not_null $filter_param_1 $filter
    touch -t $filter_param_1 $date_file_1
    filter_matched=`find $repository \! -newer $date_file_1 -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}

function filter_newer_than() {
    assert_not_null $filter_param_1 $filter
    touch -t $filter_param_1 $date_file_1
    filter_matched=`find $repository -newer $date_file_1 -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}

function filter_date_between() {
    assert_not_null $filter_param_1 $filter
    assert_not_null $filter_param_2 $filter
    touch -t $filter_param_1 $date_file_1
    touch -t $filter_param_2 $date_file_2
    filter_matched=`find $repository -newer $date_file_1 \! -newer$date_file_2 -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}

function filter_bigger_than() {
    assert_not_null $filter_param_1 $filter
    filter_matched=`find $repository -size +${filter_param_1}c -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}

function filter_smaller_than() {
    assert_not_null $filter_param_1 $filter
    filter_matched=`find $repository -size -${filter_param_1}c -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}

function filter_words() {
    assert_not_null $filter_param_1 $filter
    filter_matched=`grep -rlEw $filter_param_1 $repository`
    assert_last_cmd_success $?
    dispatch_action $*
}

function filter_namefilter() {
    assert_not_null $filter_param_1 $filter
    filter_matched=`find $repository -regextype posix-extended -regex '^.*/[^\/]*(${filter_param_1})[^\/]*$' -type f -print`
    assert_last_cmd_success $?
    dispatch_action $*
}

function filter_keep_total_size_under() {
    assert_not_null $filter_param_1 $filter

    local allfiles=`find $repository -size +1c -type f -printf %h/%f:%s\\\n |sort -rn -t \: -k 2`
    local currentsize=0
    local maxsize=$(($filter_param_1-1))
    local delete_all='false'
    for a in $allfiles ; do
        if [ '${delete_all}' == 'true' ] ; then
            rm -f `echo $a | cut -d \: -f 1` &> /dev/null
            continue
        fi

        currentsize=$(($currentsize+`echo $a | cut -d \: -f 2`))
        if [ $currentsize -gt $maxsize ] ; then
            rm -f `echo $a | cut -d \: -f 1` &> /dev/null
            delete_all='true'
        fi
    done
}

# default action
function action_list() {
    for a in ${filter_matched} ; do
        echo $a
    done
}

# print detail of each matched file.
function action_list_detail() {
    for a in ${filter_matched} ; do
        find $repository -wholename '$a' -printf %h/%f\\t%s\\t%TY-%Tm-%Td\ %TH:%Tm:%TS\\n
    done
}

function action_delete() {
    for a in $filter_matched ; do
        rm $a &> /dev/null
        assert_last_cmd_success $?
    done
}

function action_replace() {
    # sanity check
    if [ '${filter}' != '--words' ] ; then
        echo '--replace is only valid when used with --words'
        exit $ec_bad_command
    fi

    assert_not_null $action_param_1 $action
    for a in $filter_matched ; do
        sed -ire 's/${filter_param_1}/${action_param_1}/g' $a
        assert_last_cmd_success $?
    done
}

# put all matched files into a tar.gz file
function action_newrepository {
    # first we'll collect all the matched file into $tmp_file
    local tmp_file='.tmp.newrepository.lst'
    for a in $filter_matched ; do
        echo $a >> $tmp_file
    done

    # use $tmp_file to instruct tar command to create tar.gz file
    tar -zc -T $tmp_file 1> $action_param_1 2> /dev/null

    # now we should store the outcome of the previous command, because
    # when we execute the next command (rm -fR ...), the $? value would
    # be changed.
    local tar_ec=$?

    rm -fR $tmp_file &> /dev/null
    assert_last_cmd_success $tar_ec
}

#----functions end--------------------------------------------------------------


# sanity check
if [ $# = 0 ] ; then usage; fi

# exit status
ec_bad_command=-1
ec_no_match=-2
ec_unexpected_param=-3
ec_failed_command=-4
ec_bad_param=-5

# setup :- defaults
repository=~/sw302/lab05/repository
date_format=%Y%m%d%H%M.%S
date_file_1=.junk1
date_file_2=.junk2

# setup :- variables for filter
filter=$1
filter_param_1=''
filter_param_2=''
filter_param_count=0
filter_matched=''

# setup :- variables for action
action=''
action_param_1=''

# main filter dispatcher
case '${filter}' in
'--help')
    usage
;;
'--older_than' | '--newer_than')
    check_date_format $2
    shift 2
;;
'--date_between')
    check_date_format $2
    # we need to retain the first date since check_date_format always
    # write to variable filter_param_1
    tmp=$filter_param_1
    check_date_format $3
    filter_param_2=$filter_param_1
    filter_param_1=$tmp
    shift 3
;;
'--bigger_than' | '--smaller_than')
    filter_param_1=$2
    shift 2
;;
'--words' | '--namefilter')
    shift 1
    fetch_and_shift_args $*
    shift $filter_param_count
;;
'--keep_total_size_under')
    filter_param_1=$2
    shift 2
;;
*)
    usage
;;
esac

# make sure that --keep_total_size_under has no more than 1 parameter.
if [ '${filter}' == '--keep_total_size_under' ] ; then
    assert_no_more_param $*
else
    action=$1
    shift 1
fi

# invoking the filter function.  The function name is the same as removing
# the first two '--' from $filter variable.
result='`filter_${filter##--} $*`'
echo '${result:=''}'
 

No comments:

Post a Comment