8.13 Rechnungsschreibung Aufgabenstellung
Es soll eine einfache Rechnung erzeugt werden.
- Die Daten dazu liegen in einer CSV-Eingabedatei.
- Das Ende einer Rechnung ist durch end in den Daten gekennzeichet
Lernziel:
- awk aus shellscript aufrufen
- awk-Datei in separater Datei
- laden von Funktionen aus separater Datei
- Daten aus Datei lesen
- Daten in Datei schreiben.
- Formatierte Ausgabe
- Erzeugung Anwendung von eigenen awk-Funktionen
Eingabedaten
$ cat ~/bin/awk/data/awk_rechnung.dat
100,Schrauben,0.05
20,Bretter,3.89
10,Bohrer,5.85
end
200,Metallschrauben,0.07
50,Winkeleisen,4.6
$ cat awk_rechn_ausg
25.02.97 000001 21.20 162.50
25.02.97 000002 36.60 280.60
Die Datei mit der laufenden Nummer der nächsten Rechnung enthält nur einen Wert
$cat awk_rechn_next
3220032
Die Bildschirmausgabe des Programmes.
8.13.1 Rechnungschreibung Lösung
Grundsätzlich könnte alles in eine einzige Scriptdatei gepackt werden.
Wenn aber wiederverwendbare Teile enthalten sind, sollte diese ausgelagert werden.
Aufteilung macht die Programmstruktur
- übersichtlicher, da nur der Aufruf mit den Parametern erscheint
- besser wartbar, da getestete, wiederverwendete Funktionen keine Fehler enthalten sollten
line: code : awk_rechnung.sh
01: 02: 03: 04: 05: 06: 07: 08: 09: 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:
|
#! /bin/zsh # --------------------------------------------------------------------------------- # Erzeugt : 25.02.97 # Autor : Detlef Hahn # Beschreibung : Das Script erzeugt aus einer Datendatei eine Rechnung # Die Daten liegen in folgender Form vor # Menge,Artikelbezeichnung,E-Preis # Trennzeichen ist das Komma # ---------------------------------------------------------------------------------- # Modif.Log : 20.09.2022 # Umstellung von bin/ksh auf bin/zsh # ersetzen von autoload durch source bzw # durch . den alias von source #_============================================================= # # ksh kennt eine autoload Anweisung mit der angegebene Funktionen in # den Verzeichnissenin FPATH gesucht werden. # sh und bash haben diese Möglichkeit nicht #FPATH=/usr/local/lib/func_lib:/home/hahn/lib/func_lib #autoload kopf pos prompt_jn
source ~/bin/myfunc_lib/kopf
# ---------------------------------------------------------------------------------- # Definieren Sie nachfolgend Ihre eigenen Funktionen # ----------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------- # Mainscript awk_rechnung # ---------------------------------------------------------------------------------- kopf # # #awk [ options ] -f program-file [ -- ] [[-v] var=value] ... [datendatei] ... awk -f rechnung.awk -f inc/rechn_functions.awk data/awk_rechn.dat
cont less data/awk_rechn_ausg.journal echo echo "nächste Renr" cat data/awk_rechn_next
|
Da kopf eine Funktion ist, die bei meiner Bash-Progammierung mehrfach verwendet wírd,
ist sie in eine Datei in dem Verzeichnis ~/bin/myfunc_lib/ ausgelagert worden.
Sie enthält neben dem Kopf noch verschiedene Promtfunktionen
Sie wird dann in denn jeweiligen Scriptfiles per source eingebunden.
line: code : kopf
001: 002: 003: 004: 005: 006: 007: 008: 009: 010: 011: 012: 013: 014: 015: 016: 017: 018: 019: 020: 021: 022: 023: 024: 025: 026: 027: 028: 029: 030: 031: 032: 033: 034: 035: 036: 037: 038: 039: 040: 041: 042: 043: 044: 045: 046: 047: 048: 049: 050: 051: 052: 053: 054: 055: 056: 057: 058: 059: 060: 061: 062: 063: 064: 065: 066: 067: 068: 069: 070: 071: 072: 073: 074: 075: 076: 077: 078: 079: 080: 081: 082: 083: 084: 085: 086: 087: 088: 089: 090: 091: 092: 093: 094: 095: 096: 097: 098: 099: 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: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257:
|
# Diese Funktionen werden per . ~/bin/myfunc_lib in scripts eingebunden # # hier werden in verschiedenen Formen Escapesequenzen für die Terminalsteuerung erzeugt. # siehe : man tput, man terminfo , man termcap UUP=`tput cuu1` CLR_EOS=`tput ed` # clr_eos ed cd clear to end of CLR_EOL=`tput el` # clr_eol el ce clear to end of line norm=`tput sgr0` # exit_attribute_mode sgr0 me turn off all attributes tributes
cursor_normal cnorm ve make cursor appear normal normalnormal rev=`tput rev` # revers
# ANSI Escape Sequenzen siehe terminfo # ESCAPE-Zeichen gefolgt von [ Vordergrund;Hintergrund m als Abschluss #Escape kann in verschiedener Form erzeugt werden: # - durch oktale Angabe 3 # - \e Escape esc="3[" blue="${esc}[37;44m" #weiß auf blau red="\e[37;41m" #rot auf silber rot="\e[47;31m" # rot auf weiß grev="\e[39;42m" # gruen und rev grey="\e[30;47m" # schwarz auf grau green="\e[30;42m" # black auf green bold="\e[0,1m" # fett
LINIE="# -----------------------------------------------------------------------------------------------------------"
# -------------------------------------------------------------------- # Functions müssen vor der Verwendung deklariert werden # --------------------------------------------------------------------
# Trap verwenden in den verschiedenen Shells unterschiedliche Systemvariablen # bash kennt ${BASH_SOURCE} , zsh verwendet dafür ${(%):-%x} # ksh ${.sh.file} function failure() { local lineno=$1 echo -e "\e[47;31m ERR Failed at Line ${LINENO} ${lineno} ${(%):-%x} ${FUNCNAME[0]} ${norm}" STATUS=1 } # setze Trap Condition ERR , wenn ein Kommando einen Return ungleich 0 (false) zurückgibt # INT , wenn das Programm SIGINT abfangen soll. # kill -l zeigt Übersicht der Sinale mit ihrem # numerischen Äquivalent trap 'failure ${LINENO} ' ERR
# ----------------------------------------------------------------------------------------------------------- # Lösche Terminal und gebe Kopfzeilen aus function kopf() {
typeset -i p len={#1} rev=$(tput smso) #terminal capabilities zuweisen #norm=$(tput rmso) clr_eol=$(tput el) ((p=(32-l)/2+17)) clear echo -e ${blue}${clr_eol} ${bold} Datum : $(date '+%d.%m.%y')$(tput cup 0 $p) $1 $(tput cup 0 54) Uhrzeit : $(date '+%H:%M:%S') ${norm} echo -e ${grey}${clr_eol} ${grey} `uname -a` ${norm} echo }
# ---------------------------------------------------------------------------------------------------------- # auch oktal kann eine Escapesequence erzeugt werden # ---------------------------------------------------------------------------------------------------------- function show() { echo "\[3[37;44m"${clr_eol} "\[3[1m" $1 ${norm} } # -----------------------------------------------------------------------------------------------------------
#Prompt mit Argumenten # Eingabe cmd # q oder Q = Return wert 1 # -x oder + x Toggle Debug Mode #!cmd Shell escape (cmd wird ausgefuehrt) #alles andere wird in $cmd zurueckgegeben # -------------------------------------------- function prompt_yn() { abr=${quit:-"exit 1"} while echo "${*} oder (q/Q) fuer $abr: \c" >&2 read cmd do case $cmd in +x|-x) set $cmd ;; Q|q) return 1 ;; !*) eval `expr "$cmd" : "!(.*\)"` ;; *) return 0 esac done }
function prompt_jn() { i=0 while echo -n " (j/n) " read antw do case $antw in y|j|Y|J) return 0 ;; n|N) return 1 ;; *) echo "Wer lesen kann ist klar im Vorteil" (( i = i + 1 )) if [[ $i -gt 3 ]] then echo "hoffnungslos! wir geben auf" exit 1
fi ;; esac done
}
# prompt_txt question [default] :. # da return String anscheinend nicht geht (nur integer), # Rückgabe des Ergebnisses über die globale Variable RET # ksh bash # read var?prompt read -p prompt var
function lese_text() { echo "Default : " read text?" " if [ ${#text} -eq 0 ] ; then RET="" else RET=$text fi return } # -----------------------------------------------------------------------------------------------------------
function logger() { if [[ ${log} = "console" ]] # nur auf console ausgeben then echo -e "$*" else # default console und Logfile echo -e "$*" echo -e $(date "+[%H:%M:%S.%3N]") "$*" >> ${LOG_FILE} fi } # -----------------------------------------------------------------------------------------------------------
function exit_on_error () { echo -e "${1}\n${LINIE}" >> ${LOG_FILE} execute_scripts "${POST_ERROR}" # Skripte zur Fehlerbehandlung cat ${LOG_FILE} exit 2
} # ----------------------------------------------------------------------------------------------------------- # read -p " Abbruch q/Q Fortsetzung durch <CR> " dummy # funktioniert in bash aber nicht in zsh da wird ein Error geliefert function cont() { read "dummy? Abbruch q/Q Fortsetzung durch <CR> " if [ $? = 1 ] || [ "${dummy}" = "q" ] || [ "${dummy}" = "Q" ] then exit 1 fi echo -e "${CUUP}${CLR_EOS}" }
# *************************************************************************** # This function removes whitespace at the beginning and end of a string. # # @param string string (optional, can also handle STDIN) # @return string # @example: echo " That is a sentinece " | trim function trim() { if [ -n "" ]; then echo $1 | sed 's/^[[:space:]]*//g' | sed 's/[[:space:]]*$//g' else cat - | sed 's/^[[:space:]]*//g' | sed 's/[[:space:]]*$//g' fi }
# *************************************************************************** # This function removes whitespace at the end of a string. # # @param string string (optional, can also handle STDIN) # @return string # @example: echo "That is a sentinece " | rtrim
function rtrim() { if [ -n "" ]; then echo -n "" | sed 's/[[:space:]]*$//g' else cat - | sed 's/[[:space:]]*$//g' fi }
# *************************************************************************** # This function removes whitespace at the beginning of a string. # # @param string string (optional, can also handle STDIN) # @return string # @example: echo " That is a sentinece " | trim
function ltrim() { if [ -n "" ]; then echo -n "" | sed 's/^[[:space:]]*//g' else cat - | sed 's/^[[:space:]]*//g' fi }
# ************************************************************** # strim -- cut a tring after X chars and append three points # # string strim( string string [, int length ] ) function strim () { local string="" local length=${2:-30} [ "${#string}" -gt ${length} ] && string="${string:0:${length}}..." echo $string }
# ************************************************************** # filename -- extract the filename from file path # # string filename( string string )
function filename() { local str="$*" str=$( echo $str | egrep -o '([^\/]+)$' | cut -d? -f1 ) echo $str }
# *************************************************************************** # This function encode a string in md5 hash of 32 characters. You can short # the length with the second parameter. # # @param string string (required) # @param integer length (option, default: 32) # @return string # @example: md5 "Hello World" 8
function md5() { local length=${2:-32} local string=$( echo "$1" | md5sum | awk '{ print }' ) echo ${string:0:${length}} } # -------------------------------------------------------------------
|
line: code : rechnung.awk
01: 02: 03: 04: 05: 06: 07: 08: 09: 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:
|
BEGIN { summe=0 ; zwsumme=0; MWST=15; pos = 0 ; FS="," # Fieldseparator auf Komma setzen datum=strftime("%d.%m.%y",systime()) # Tagesdatum # getline splittet nicht in {phpHighlight} bis $n , # Return > 0 OK == 0 EOF gelesen, < 0 Fehler if( !(getline renr < "data/awk_rechn_next" > 0)) { renr=strftime("%y0000") } # print "gelesen " renr } /^end/ { abschl() # Rechnungsummen ausgeben next # nächsten Satz lesen }
{ # gilt für alle Eingabezeilen pos++ # Posten hochzählen if (pos == 1) # wenn 1. Posten dann Kopfzeilen rechn_kopf() # ausgeben split($0,zeile) # Splitt Eingabe nach Zeile wert = zeile[1] * zeile[3] # Index läuft ab 1 # geht natürlich auch über * printf("%3d : %5d %-20s %7.2f %9.2f\n",pos, $1, $2, $3, wert) zwsumme += wert # Zwischensumme bilden }
END { if (pos > 0 ) # wenn letzte Rechnung nicht durch end abschl() # abgeschlossen ist, dann Abschluß; printf("\n\n\t\tgespeichert nächste RECHNUNGSNR: %8d\n\n", renr) print renr > "data/awk_rechn_next" # Der Dateiname muß gequoted werden, # da er sonst vom awk als Variable # interpretiert wird }
|
line: code : rechn_functions.awk
01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
|
function rechn_kopf() { printf("\f\t\t***** RECHNUNG ***** \n") printf("\t\t\t\t DATUM : %s\n", datum) printf("\t\t\t\t RECHNUNGSNR: %8d\n\n", renr) printf("LFDNR STÜCK BEZEICHNUNG \t\tE-PREIS BETRAG\n") }
function abschl() { mwst = (zwsumme * MWST) / 100 gsumme = zwsumme + mwst printf "%40s________________\n"," " printf "%29s Zwischensumme : %9.2f\n"," ",zwsumme printf "%29s Mehrwertsteuer : %9.2f \n"," ",mwst printf "%40s________________\n"," " printf "%29s Gesamtsumme : %9.2f \n"," ",gsumme total_mwst += mwst # Totalsumme MWST bilden total_summe+= gsumme # Totalsumme # Satz für Rechnungsausgangsjournal schreiben printf "%8s %06d %9.2f %9.2f\n", datum, renr, mwst, gsumme >> "data/awk_rechn_ausg.journal" zwsumme = 0 gsumme = 0 renr++ # erhöhen für nächste Rechnung pos = 0 }
|
alte Lösung
<
#! /bin/ksh
# ---------------------------------------------------------------------------------
# Erzeugt : 25.02.1997
# Autor : Detlef Hahn
# Beschreibung : Das Script erzeugt aus einer Datendatei eine Rechnung
# Die Daten liegen in folgender Form vor
# Menge,Artikelbezeichnung,E-Preis
# Trennzeichen ist das Komma
# ----------------------------------------------------------------------------------
# Definieren Sie nachfolgend Ihre eigenen Funktionen
# ----------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------
# Mainscript awk_rechnung
# ----------------------------------------------------------------------------------
kopf
awk -f awk_rechnung awk_rechn.dat
BEGIN { summe=0 ; zwsumme=0; MWST=15; pos = 0 ;
FS="," # Fieldseparator auf Komma setzen
datum=strftime("%d.%m.%y",systime()) # Tagesdatum
getline renr < "awk_rechn_next" # renr= system(cat awk_rechn_last)
}
/^end/ { abschl() # Rechnungsummen ausgeben
next # näqchsten Satz lesen
}
{ # gilt für alle Eingabezeilen
pos++ # Posten hochzählen
if (pos == 1) # wenn 1. Posten dann Kopfzeilen
rechn_kopf() # ausgeben
split($0,zeile) # Splitt Eingabe nach Zeile
wert = zeile[1] * zeile[3] # Index läuft ab 1
# geht natürlich auch über $1 * $3
printf("%3d : %5d %-20s %7.2f %9.2f\n",pos, $1, $2, $3, wert)
zwsumme += wert # Zwischensumme bilden
}
END { if (pos > 0 ) # wenn letzte Rechnung nicht durch end
abschl() # abgeschlossen ist, dann Abschluß
print renr > "awk_rechn_next" # Der Dateiname muß gequoted werden,
# da er sonst vom awk als Variable
# interpretiert wird
}
function rechn_kopf()
{ printf("\f\t\t***** RECHNUNG ***** \n")
printf("\t\t\t\t DATUM : %s\n", datum)
printf("\t\t\t\t RECHNUNGSNR: %8d\n\n", renr)
printf("LFDNR STÜCK BEZEICHNUNG \t\tE-PREIS BETRAG\n")
}
function abschl()
{ mwst = (zwsumme * MWST) / 100
gsumme = zwsumme + mwst
printf "%40s________________\n"," "
printf "%29s Zwischensumme : %9.2f\n"," ",zwsumme
printf "%29s Mehrwertsteuer : %9.2f \n"," ",mwst
printf "%40s________________\n"," "
printf "%29s Gesamtsumme : %9.2f \n"," ",gsumme
total_mwst += mwst # Totalsumme MWST bilden
total_summe+= gsumme # Totalsumme
# Satz für Rechnungsausgangsjournal schreiben
printf "%8s %06d %9.2f %9.2f\n", datum, renr, mwst, gsumme >>
"awk_rechn_ausg"
zwsumme = 0
gsumme = 0
renr++ # erhöhen für nächste Rechnung
pos = 0 # zurücksetzen Postenzähler
}