Imperatív programozás 11.
Unix filterek implementálása
A UNIX szűrők (filters) olyan programok, amelyek valami elemi tevékenységet végeznek el. Ilyen filter a cat, grep, diff és még sok más program. Ezek a programok tipikusan a standard inputról olvasnak EOF-ig és az eredményt a standard outputra írják. Amennyiben a programok egy vagy több fájlnév paramétert kapnak, akkor a standard input heyett ezekről a fájlokról olvasnak. Az így megírt programokat kényelmesen és sokoldalúan lehet pipe-okba szervezni.
hexdump
Az input állomány(oka)t hexadecimális formátumban írja ki 16 bájtonként. A 16 elemű sor kiírás előtt megelenik a sor címe (szintén hexadecimálisan), utána pedig maguk a karakterek íródnak ki ASCII formátumban (a nem megjeleníthető karakterek helyett pedig egy pont karakter).
A program implementációja C-ben
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
#include <stdio.h>
#include <ctype.h> /* az isgraph() miatt */
#define LINESIZE 16
void hd(FILE *in, FILE *out);
void print(FILE *fp,long addr,unsigned char *buf,int len);
int main(int argc, char *argv[])
{
int err = 0; /* ha legalább egy fájl megnyitása sikertelen
akkor jelezni fogjuk ezt az exit kódban */
if ( argc < 2 )
{
hd( stdin, stdout); /* nincs fájl argumentum */
}
else
{
int i;
for ( i = 1; i < argc; ++i ) /* a fájl argumentumok */
{
FILE *fp = fopen( argv[i], "r");
if ( NULL != fp ) /* sikeres megnyitás */
{
hd( fp, stdout);
fclose( fp); /* véges számú fájl lehet megnyitva */
}
else
{
fprintf( stderr, "Can't open %s\n", argv[i]);
err = 1; /* legalább egy fájl megnyitási hiba */
}
}
}
return err; /* 0 == ok, 1 == volt hiba */
}
void hd( FILE *in, FILE *out) /* out paraméter későbbi */
{ /* továbbfejlesztéshez */
char ch;
unsigned char buffer[LINESIZE]; /* "%x" miatt unsigned */
int cnt = 0;
long addr = 0L; /* a címet mi fájlon belül számoljuk */
while ( EOF != (ch = fgetc(in)) )
{
buffer[cnt++] = ch;
if ( LINESIZE == cnt ) /* buffer teli */
{
print( out, addr, buffer, cnt); /* buffer kiírása */
addr += cnt; /* == BUFSIZE, léptetjük a címet */
cnt = 0;
}
}
/* ha a bufferben még maradt karakter, amikor a fájlunk
véget ér, akkor azokat még ki kell írnunk */
if ( cnt > 0 )
{
print( out, addr, buffer, cnt);
}
}
void print(FILE *out,long addr,unsigned char *buf,int len)
{
int i;
fprintf( out, "%08lx | ", addr); /* cím hexa-ban */
for ( i = 0; i < len; ++i )
{
fprintf( out, " %02x", buf[i]); /* karakter hexa-ban */
}
for ( ; i < LINESIZE; ++i) /* utolsó sor végi space-ek */
{
fprintf( out, " ");
}
fprintf( out, " | ");
for ( i = 0; i < len; ++i) /* maga a karakter vagy '.' */
{
fprintf( out, "%c", isgraph(buf[i]) ? buf[i] : '.');
}
fprintf( out, "\n"); /* a kiírt sor legvége */
}
Figyeljük meg a buf buffer unsigned char típusát. Erre azért van szükség, mert a “%x” format előjeltelen egész számot vár. A 128..255 közötti kódú char értékek bizonyos implementációkban lehetnek “negatívak”, és ezek igen nagy pozitív unsigned int értékekre konvertálódnának, ami hatására a kiírt hexadecimális kód például valami ilyesmi lenne: ffffff0a. Az unsigned char értékek viszont garantáltan mindig a 0..255 közötti nemnegatív egész értékekre képződnek le.
Az eredeti UNIX utility úgy működik, hogy több input fájl esetén az outputot összefűzi és a címek folytonosan növekednek. A mi programunk nem fűzi össze az outputot, és a címeket is nulláról kezdi minden input esetében.
Házi feladat a program módosítása a UNIX utility viselkedése szerint.
A program implementációja Python3-ban
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
#!/usr/bin/env python3
from curses import ascii
import io
import sys
def hd(input, output):
addr = 0
line_size = 16
buffer = input.read(line_size)
while buffer != b"":
printer(output, addr, buffer, line_size)
addr+= line_size
buffer = input.read(line_size)
def printer(output, addr, buf, line_size):
line_in_hex = " ".join("{:02x}".format(c) for c in buf) \
.ljust(line_size*2+line_size)
replace_non_ascii = \
lambda a: chr(a) if ascii.isgraph(a) else "."
line_in_text = "".join(replace_non_ascii(c) for c in buf)
formatted_line = "{:08x} | {} | {}". \
format(addr, line_in_hex, line_in_text)
print(formatted_line, file=output)
def main():
if len(sys.argv) < 2:
hd(sys.stdin.buffer, sys.stdout)
else:
for arg in sys.argv[1:]:
try:
with io.open(arg,'rb') as file_content:
hd(file_content, sys.stdout)
except IOError as ioerr:
print("Can't open " + arg)
if __name__ == "__main__":
main()