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[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 -${blue}${clr_eol} ${boldDatum : $(date '+%d.%m.%y')$(tput cup 0 $p) $$(tput cup 0 54Uhrzeit : $(date '+%H:%M:%S') ${norm}
       echo -
${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"  $${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|-xset $cmd ;;
                
Q|q) return 1   ;;
                !*)  eval `
expr "$cmd" : "!(.*\)"` ;;
                *)  return 
0
        esac 
        done
}


function 
prompt_jn() {
  
i=
  
while  echo -"  (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"
          
(( ))
          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 -"$*"
     
else                           # default console und Logfile
        
echo -"$*"
        
echo -$(date "+[%H:%M:%S.%3N]""$*" >> ${LOG_FILE}
     
fi
}
# -----------------------------------------------------------------------------------------------------------


function exit_on_error () {
      echo -
"${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  [ $? = ] || [ "${dummy}"q" ] || [ "${dummy}"Q" ]
        
then
                
exit 1
        fi
        
echo -"${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 [ -
"" ]; then
        
echo $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 [ -
"" ]; then
        
echo -"" 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 [ -
"" ]; then
        
echo -"" 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=zwsumme=0MWST=15pos ;
        
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, $3wert)
 
        
zwsumme += wert                    # Zwischensumme bilden
      
}

END   { if (pos )                      # 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"datumrenrmwstgsumme >>  "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 }