In der täglichen Arbeit ergibt sich immer wieder die Aufgabe, aus dem IFS Dateien auszulesen. Hier erfahren Sie, welche Möglichkeiten es gibt und am Ende gibt es einen Vergleich der Methoden.
Folgende Methoden sind auf der IBM i verfügbar
- Der Befehl ls in der Unix Shell
- Das Command RTVDIRINF
- Die API’s OpenDir, ReadDir und CloseDir
- Die SQL-Funktion QSYS2.IFS_OBJECT_STATISTICS
1. Der Befehl ls in der Unix Shell
Auf meiner Maschine habe ich das IFS-Verzeichnis /home/export/test, das ich auslesen möchte und das Ergebnis soll ins gleiche Verzeichnis in die Datei dirlist.txt geschrieben werden. Dazu starte ich die QSHELL mit QSH und gebe folgenden Befehl ein:
ls -ltR --time-style=long-iso /home/export/test > /home/export/test/dirlist.txt
ls Unix Command list
-ltR l = langes Listformat, t = sortiert nach Datum,
R = zeigt rekursiv die Unterordner an
--time-style=long-iso ISO-Datumsformat
> Umleitung des Ergebnisses in eine Ausgabedatei
Wenn ich den Befehl in ein CL-Programm einbetten möchte, dann geht das mit diesem Command:
QSH CMD('ls -ltR --time-style=long-iso /home/export/test > /home/export/test/dirlist.txt')
Mein persönlicher Favorit ist das Ausführen des Commands im ACS:
CL:QSH CMD('ls -ltR --time-style=long-iso /home/export/test > /home/export/test/dirlist.txt')
Mit WRKLNK auf die Ausgabedatei erhalte ich folgendes Ergebnis:
/home/export/test:
total 50216
-rw-rw-rw- 1 ross 0 19 2024-09-09 17:26 dirlist.txt
-rw-rw-rw- 1 ross 0 23094459 2024-08-26 14:53 MyFiles2.txt
-rw-rw-rw- 1 ross 0 5378 2024-08-26 14:49 MyFiles.txt
-rwxrwxrwx 1 ross 0 2233409 2024-08-26 14:04 MyCSVTest10.csv
-rwxrwxrwx 1 ross 0 2233409 2024-08-26 14:04 MyCSVTest1.csv
-rwxrwxrwx 1 ross 0 2233409 2024-08-26 14:04 MyCSVTest2.csv
Mit der SQL-Funktion QSYS2.IFS_READ kann ich die Ausgabedatei bequem auslesen. Sie ist ab 7.3 TR9 und 7.4 TR3 verfügbar:
SELECT LINE_NUMBER, LINE
FROM TABLE(QSYS2.IFS_READ('/home/export/test/dirlist.txt'));
Das ergibt folgendes Ergebnis:

Ich bin nur an der Größe der Datei, dem Erstelldatum und dem Dateinamen interessiert und modifiziere mein SQL:
SELECT LINE_NUMBER,
substring(LINE, 21, 8) as Size,
substring(LINE, 30, 16) as CreateDate,
substring(LINE, 47, 128) as File
FROM TABLE(QSYS2.IFS_READ('/home/export/test/dirlist.txt'));

Das ls Command lässt sich sehr einfach in ein CL-Programm einbetten und die Ausgabe in eine PF MYLIB/DIRLIST umleiten. Um das Programm multiuserfähig zu machen, bitte anstatt der Bibliothek MYLIB die QTEMP verwenden:
PGM PARM(&PATH)
DCL VAR(&PATH) TYPE(*CHAR) LEN(256)
DCL VAR(&CMD) TYPE(*CHAR) LEN(256)
DCL VAR(&LIB) TYPE(*CHAR) LEN(10) VALUE(MYLIB)
DCL VAR(&OBJ) TYPE(*CHAR) LEN(10) VALUE(DIRLIST)
DLTF FILE(&LIB/&OBJ)
MONMSG MSGID(CPF0000)
OVRDBF FILE(STDOUT) TOFILE(&LIB/&OBJ) OVRSCOPE(*CALLLVL)
CHGVAR VAR(&CMD) VALUE('ls -lt --time-style=long-iso' +
*BCAT %TRIM(&PATH))
QSH CMD(&CMD)
DLTOVR FILE(STDOUT) LVL(*)
ENDPGM
Mit dem Aufruf des CL-Programms im ACS und SQL auf die Datei MYLIB/DIRLIST
CL:CALL PGM(MYLIB/DIRLESC) PARM(('/home/export/test'));
select * from MYLIB.DIRLIST;
erhalte ich folgendes Ergebnis:

Auch hier bin nur an der Größe der Datei, dem Erstelldatum und dem Dateinamen interessiert und modifiziere mein SQL:
SELECT substring(SRCDTA, 21, 8) as Size,
substring(SRCDTA, 30, 16) as CreateDate,
substring(SRCDTA, 47, 128) as File
FROM MYLIB.DIRLIST;
Ergebnis:

2. Das Command RTVDIRINF
Es erstellt zwei PF, eine mit der Endung D für die Directory Info und eine mit der Endung O für die Objekt Info. Man kann auswählen, ob man alle Unterverzeichnisse mit ausgeben will = SUBTREE(*ALL) und man kann die Namen der Ausgabedatei und der Bibliothek der Datei angeben. Es ist wichtig, die Ausgabedateien vorher zu löschen, da das Command diese nicht löscht bzw. überschreibt.
CL:DLTOBJ OBJ(MYLIB/DIRINF*) OBJTYPE(*FILE);
CL:RTVDIRINF DIR('/home/export/test') SUBTREE(*ALL)
INFFILEPFX(DIRINF) INFLIB(MYLIB)
Mit diesem Command erhält man eine große Zahl an Informationen und ich selektiere in diesem Fall nur eine kleine Auswahl mit folgenden SQL-Anweisungen:
set current schema 'MYLIB';
select QEZDIRIDX as Index, QEZDIRNAM1 as Directory
from DIRINFD;
select QEZDIRIDX as Index, QEZOBJNAM as Name,
QEZOBJTYPE as Type, QEZCCSID as CCSID, QEZDTASIZE as Size,
date(QEZCRTTIM) concat ' ' concat time(QEZCRTTIM) as Created,
date(QEZCHGTIMD) concat ' ' concat time(QEZCHGTIMD) as Changed,
date(QEZACCTIM) concat ' ' concat time(QEZACCTIM) as Access,
QEZOWN as Owner
from DIRINFO;
Ergebnis des ersten SQL auf die Datei DIRINFD:

Ergebnis des zweiten SQL auf die Datei DIRINFO:

Für die weitere Verarbeitung benötige ich den kompletten Pfad der Dateien. Das erreiche ich mit folgendem, komplexeren SQL:
with x as (
select A.QEZDIRIDX as Index, A.QEZDIRNAM1 as Directory,
B.QEZOBJNAM as Name,
trim(A.QEZDIRNAM1) concat trim(B.QEZOBJNAM) as Path,
B.QEZOBJTYPE as Type,
B.QEZCCSID as CCSID, B.QEZDTASIZE as Size,
date(B.QEZCRTTIM) concat ' ' concat time(B.QEZCRTTIM) as Created,
date(B.QEZCHGTIMD) concat ' ' concat time(B.QEZCHGTIMD) as Changed,
date(B.QEZACCTIM) concat ' ' concat time(B.QEZACCTIM) as Access,
B.QEZOWN as Owner
from DIRINFD A
join DIRINFO B on A.QEZDIRIDX = B.QEZDIRIDX
)
select * from x
where TYPE = '*STMF';
Ergebnis:

3. Die API’s OpenDir, ReadDir und CloseDir
Diese API’s gibt es schon sehr lange und sie funktionieren mit allen Betriebssystemversionen. Das Lesen der Verzeichnisinhalte ist sehr schnell – das sind in C geschriebene API’s – aber der Gehalt an Information ist gering. Für weitere Informationen, wie letzter Zugriff, Dateigröße, Objekttype oder CCSID muss eine weitere API, stat64, verwendet werden. Im folgenden Programmbeispiel lese ich den Inhalt des Testverzeichnisses und schreibe das Ergebnis in ein Array = IFSArray.
ctl-opt main(main) dftactgrp(*no) option(*nounref);
//------------------------------------------------------------------//
// Directory lesen // // //
// R.Ross 09.2024 * //
//------------------------------------------------------------------//
// Open-Directory //
//------------------------------------------------------------------//
dcl-pr opendir pointer extproc(*dclcase);
Name pointer value options(*string);
end-pr;
//------------------------------------------------------------------//
// Read-Directory //
//------------------------------------------------------------------//
dcl-pr readdir pointer extproc(*dclcase);
Dir_p pointer value options(*string);
end-pr;
//------------------------------------------------------------------//
// Close-Directory //
//------------------------------------------------------------------//
dcl-pr closedir int(10) extproc(*dclcase);
Name pointer value options(*string);
end-pr;
//------------------------------------------------------------------//
// Array Directory Data //
//------------------------------------------------------------------//
dcl-s DirEnt_p pointer; // DirectoryEntryPtr
dcl-ds DsDirent qualified based(DirEnt_p);
*n char(16);
*n uns(10);
Fileno uns(10);
Reclen uns(10);
*n int(10);
*n char(08);
NlsInfo char(12);
NlsCcsid int(10) overlay(nlsinfo:01);
NlsCntry char(02) overlay(nlsinfo:05);
NlsLang char(03) overlay(nlsinfo:07);
NlsReserv char(03) overlay(nlsinfo:10);
Namelen uns(10);
Name char(640);
end-ds;
//------------------------------------------------------------------//
// IFS-Array //
//------------------------------------------------------------------//
dcl-ds IFSArray Dim(*Auto:10000) qualified inz;
Path varchar(256);
Size int(20);
end-ds;
//------------------------------------------------------------------//
// Variablen //
//------------------------------------------------------------------//
dcl-s GblSize int(20); // File-Size
//------------------------------------------------------------------//
// Main //
//------------------------------------------------------------------//
dcl-proc main;
dcl-s LocDir varchar(256); // Directory
dcl-s LocFile varchar(256); // File
dcl-s LocPath varchar(256); // Path
dcl-s LocSize like(GblSize); // File Size
dcl-s LocDir_p pointer; // Directory-Pointer
dcl-s LocInd uns(10); // Index
clear IFSArray;
LocDir = '/home/export/test';
LocDir_p = opendir(LocDir); // Open Directory
if LocDir_p = *null; // Directory not exist
return;
endif;
dou DirEnt_p = *null; // Loop ReadDir
DirEnt_p = readdir(LocDir_p); // ReadDir
if DirEnt_p <> *null; // DirEntry exist
LocFile = %str(%addr(DsDirent.Name)); // FileName
if LocFile <> '.' and LocFile <> '..';
LocPath = %trim(LocDir) + '/' + %trim(LocFile);
LocSize = getFileSize(LocPath); // Get Filesize
LocInd += 1;
IFSArray(LocInd).Path = LocPath;
IFSArray(LocInd).Size = LocSize;
endif;
endif;
enddo;
closedir(LocDir); // Close Directory
end-proc;
//------------------------------------------------------------------//
// Get File Size //
//------------------------------------------------------------------//
dcl-proc getFileSize;
dcl-pi *n like(GblSize);
PiStmf varchar(256) const options(*varsize:*trim);
end-pi;
// Fileinfo Large Files
dcl-pr stat64 int(10) extproc(*dclcase);
Stmf pointer value options(*string);
Data likeds(DsStat64);
end-pr;
dcl-ds DsStat64 qualified inz; // Array Stat64
Size int(20) pos(17);
AccTime int(10) pos(25);
ChgTime int(10) pos(29);
CrtTime int(10) pos(33);
ObjType char(10) pos(61);
CCSID uns(05) pos(127);
end-ds;
dcl-s LocRc int(10); // ReturnCode
LocRc = stat64(PiStmf:DsStat64); // stat64 API
return DsStat64.Size; // FileSize
end-proc;
//------------------------------------------------------------------//
4. Die SQL Funktion QSYS2.IFS_OBJECT_STATISTICS
Aus meiner Sicht ist diese SQL-Funktion die einfachste Möglichkeit, viele Informationen aus den IFS-Verzeichnissen zu gewinnen und der weitere Vorteil ist, dass sie schnell ist. In Verbindung mit der SQL-Funktion QSYS2.IFS_UNLINK können beispielsweise alle Objekte in einem Verzeichnis gelöscht werden, die eine bestimmte Größe haben oder ein bestimmtes Alter. Wie das geht, habe ich in meinem letzten Artikel beschrieben: IFS-Dateien mit SQL löschen.
Achtung: Oft sind Pfadnamen mit Sonderzeichen versehen und dann fehlen diese in der Auflistung, deshalb der CAST für den Pfadnamen nach UTF8 = CCSID 1208.
SELECT CAST(PATH_NAME as varchar(128) CCSID 1208) AS PATH_NAME,
OBJECT_TYPE, DATA_SIZE, CREATE_TIMESTAMP,
ACCESS_TIMESTAMP, DATA_CHANGE_TIMESTAMP,
OBJECT_CHANGE_TIMESTAMP
FROM TABLE(
QSYS2.IFS_OBJECT_STATISTICS(
START_PATH_NAME => '/home/export/test',
SUBTREE_DIRECTORIES => 'YES',
OBJECT_TYPE_LIST => '*ALLSTMF *ALLDIR'
)
)
ORDER BY PATH_NAME;
Ergebnis:

Mit folgendem SQL wird die Anzahl der Objekte und die Summe der Objektgrößen eines IFS-Verzeichnisses einschließlich der Unterverzeichnisse ermittelt:
SELECT count(*) as Files, sum(Data_Size) as Size
FROM TABLE(
QSYS2.IFS_OBJECT_STATISTICS('/home/export/test', 'YES', '*ALLSTMF')
);
Ergebnis:

5. Performance Vergleich der unterschiedlichen Methoden
Methode | 10 Objekte | 1.000 Objekte | 200.000 Objekte |
ls | 100 ms | 200 ms | 20 s |
RTVDIRINF | 600 ms | 1.000 ms | 60 s |
API ReadDir Basis | 4 ms | 10 ms | 1,2 s |
API ReadDir Erweitert | 5 ms | 20 ms | 10 s |
IFS_OBJECT_STATISTICS | 10 ms | 200 ms | 25 s |
6. Bewertung der Methoden
ls
- Vorteile:
- Läuft auf alten Systemen vor 7.3
- Ist relativ schnell
- Einfach in der Handhabung
- Subtrees sind per Parameter möglich
- Nachteile:
- Es muss eine Zwischendatei erzeugt werden: PF oder IFS-File
- Wenige Felder
- Läuft nur in der QSHELL
RTVDIRINF
- Vorteile:
- Läuft auf alten Systemen vor 7.3
- Einfach in der Handhabung
- Sehr viele Informationen über die einzelnen Objekte
- Subtrees sind per Parameter möglich
- Nachteile:
- Es muss eine PF als Zwischendatei erzeugt werden
- Ist die langsamste Methode
API ReadDir
- Vorteile:
- Läuft auf alten Systemen vor 7.3
- Die schnellste Methode – wenn nur die Objektnamen benötigt werden, dann dauert es nur 1,2 Sekunden um 200.000 Objekte zu lesen
- Nachteile:
- Programmierung ist aufwändig
- Wenig Informationen über die Objekte
- Subtrees sind nicht möglich
IFS_OBJECT_STATISTICS
- Vorteile:
- Einfach zu programmieren
- Sehr viele Informationen über die einzelnen Objekte
- Kann leicht mit andern IFS SQL Funktionen kombiniert werden z. B. IFS_UNLINK zum Löschen von IFS Objekten
- Es ist kein CL oder RPG Programm notwendig, um es auszuführen
- Subtrees sind per Parameter möglich
- Nachteile:
- Läuft erst ab 7.3
- Läuft erst ab 7.3
Viel Spaß beim Ausprobieren.
Den Autor Rainer Ross erreichen Sie unter:
Rainer Ross IT-Beratung,
Bgm.-Hollweck-Str. 6,
85599 Parsdorf
Ausgezeichnet mit dem Innovationspreis der IBM und Spezialist für Webservices und Webanwendungen auf IBM i so schnell wie Greenscreen
Beispiel: www.myhofi.com/myapps/HTML/Myapp.html
Tel. (+49) 151/684 375 53 oder 089/413 252 94,
E-Mail: rainer_ross@web.de
Web: www.myhofi.com – Hotels finden – leicht gemacht
Für 88 Euro gibt’s hier sechs Monate lang tiefgreifendes IBM i und SQL Wissen. Hier kann man abonnieren.