CodeMagic: bashtail - a script version of the Unix tail command
The program bashtail simulates a simple version of the Unix tail command written in bash. To run the script, make sure bashtail.sh is executable, and then enter the following:
./bashtail -n NUMBER_OF_LINES_TO_SHOW SOME_INPUT_FILE
bashtail uses a circular buffer to retain the last n lines read from standard input or from a file. The buffer is written out in proper order when the input stream is finished.
This bash script demonstrates the proper use of local namespaces and it modularizes the work of the program through functional decomposition. To allow a function to pass back a return value, a technique similar to globbing in Perl is used. The function wishing to set a return value evals a variable name, provided as a string by the caller, into the caller’s symbol table. As a side effect, eval sets the value for the variable returned to the caller.
bashtail includes a small self-test harness which can be run using the -t argument. The output of bashtail is compared to that of tail and a short report of the tests passed is produced.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | #!/bin/bash # # bashtail simulates a simple version of tail written in bash. # A simple way to run the script is by invoking: # # bashtail -n NUMBER_OF_LINES_TO_SHOW SOME_INPUT_FILE # # bashtail uses a circular buffer to retain the last n lines # read from stdin or from a file. The buffer is written out in # proper order when the input stream is finished. # # The script demonstrates the proper use of local namespaces # and it modularizes the work of the program through # functional decomposition. To allow a function to pass # back a return value, a technique similar to globbing in # Perl is used. The function wishing to set a return value # evals a variable name, provided as a string by the caller, # into the caller's symbol table. As a side effect, eval sets # the value for the variable returned to the caller. # # bashtail includes a small self-test harness which can be # run using the -t argument. The output of bashtail is # compared to that of tail and a short report of the tests # passed is produced. # # Copyright (c) 2008 Technetra Corp # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation # files (the "Software"), to deal in the Software without # restriction, including without limitation the rights to use, # copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following # conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # RB_ITER= _read_lines() { local rbc=$1 local N=$2 local i=0 local line_count=0 local oldIFS="$IFS"; IFS= while read l; do line_count=$((line_count+1)) i=$((i%N)) ary[i++]="$l" done IFS="$oldIFS" RB_ITER=$i eval "$rbc=$line_count" } _print_buf() { local line_count=$1 local N=$2 local j=0 # $RB_ITER points to start of ring buffer when line_count >= N # else to one past end of ring buffer when line_count < N if [[ line_count -lt N ]]; then RB_ITER=0 N=$line_count fi while [[ j++ -lt N ]]; do echo "${ary[RB_ITER++%N]}" done } usage() { echo "SYNOPSIS:" echo -e "\t$0 [OPTION] [FILE]" echo "DESCRIPTION:" echo -e "\tOutput last part of file FILE. " echo -e "\tWhen no FILE is given or when FILE is -, read standard input" echo -en "\tExample: echo -e " echo "\"a\\nb\\nc\\n\\nd\"|$0 -n 3" echo "OPTIONS:" echo -e "\t-h\tThis help message" echo -e "\t-n N\tOutput last N lines" echo -e "\t-t\tTest against real tail using above example while" echo -e "\t\tvarying n from 6 down to 0" exit } test_script() { local passed=0 local total=0 local TAIL=/usr/bin/tail local result local target echo "testing tabs, spaces and newlines from standard input with simple echo..." for i in $(seq 6 -1 0); do total=$((total+1)) result=$(echo -e " a\\n\\tb\\nc\\n\\nd"|$0 -n $i) target=$(echo -e " a\\n\\tb\\nc\\n\\nd"|$TAIL -n $i) if [[ "$result" == "$target" ]]; then passed=$((passed+1)) else echo "failed simple echo test $i \"echo -e " a\\n\\tb\\nc\\n\\nd"|$0 -n $i\"" fi done echo "testing file input..." for i in $(seq 10 -1 0); do total=$((total+1)) result=$($0 -n $i /etc/passwd) target=$($TAIL -n $i /etc/passwd) if [[ "$result" == "$target" ]]; then passed=$((passed+1)) else echo "failed file input test $i" fi done echo "testing standard input from file..." for i in $(seq 10 -1 0); do total=$((total+1)) result=$(cat /etc/passwd|$0 -n $i ) target=$(cat /etc/passwd|$TAIL -n $i ) if [[ "$result" == "$target" ]]; then passed=$((passed+1)) else echo "failed file input from stdin, test $i" fi done echo "passed $passed out of $total tests" exit } get_args() { local N_RET=$1; shift local FN_RET=$1; shift local arg=$1 local N local FN case "$arg" in -h) usage;; -n) shift; N=$1; shift; FN=$1;; -t) test_script;; esac [[ -z "$N" ]] && usage [[ $N -lt 1 ]] && exit eval "$N_RET=$N; $FN_RET=$FN" } read_lines() { local result=$1 local rb_count if [ "$FILE_NAME" -a "$FILE_NAME" != "-" ]; then _read_lines rb_count $HOW_MANY_LINES < $FILE_NAME else _read_lines rb_count $HOW_MANY_LINES fi eval "$result=$rb_count" } print_buf() { local rb_count=$1 _print_buf $rb_count $HOW_MANY_LINES } # # MAIN # get_args HOW_MANY_LINES FILE_NAME $@ read_lines count print_buf $count |

Copyright © 2008 Technetra. This code is covered by the MIT license. You can follow any responses to this entry through the RSS 2.0 feed. You can skip to the end and leave a response. Pings are currently closed.