kitvb1
8th March 2010, 21:01
This Article was published in the March 2010 edition of iSeries Nation Network newsletter. The original article will be found in the Tekkie-Corner (Chapter 3) at http://www.sss-software.de/inn/powerinfo/iNN-PowerInfo.pdf
Listing a Directory Tree Contents
Kit von Blerk
My task recently was to attach x number of attachments to an email. “Piece of cake”, I hear you say. Allow me to explain the challenge then.
The challenge here is that the IFS path as well as the extension of the document e.g. .PDF, .DOCX, .XLS, etc. (we will call them files for the duration of this article), is not known at the time the user chooses which file should be attached to which outgoing document type e.g. order confirmation, delivery note, invoice, etc. (we will call them documents for the duration of this article).
In this TechTip, we will see that with just a few simple commands, one can obtain a wealth of information about the IFS file(s) to present to the user. But before we start, let me add: Yes, I know we could find a file in a less complicated way, and yes, I know we could make it more elegant as well – but I use this as an example only. Please note that this TechTip is written for v5r2.
What we have
The user currently has the facility to add (header and/or footer) text to outgoing documents which also includes a language delimiter and is defined by a 5 character Code field. Any number of Text Codes could be added to a document. A choice of text language is also available.
So for the purpose of this exercise, we’ll assume the following:
Text Code = TOC
Year = 10
Language = F
So, your IFS structure might look something similar to picture below, with these 6 files occurring in each sub-directory of the Attachments directory.
190
**click to enlarge
What we want
Let’s say the user wants to add the latest Terms and Conditions (TOC) to a document. But the TOC for 2009 is different to those for 2010, and the TOC for USA is different to those for Germany, and the TOC for customer A is different to those for customer B… and so on.
Let’s do it
Now let’s get the tree structure. To keep it simple, we’ll use the FIND QShell command and override the STDOUT output to a file.
If we wanted just the directory, it would be simple from there on - and I would have nothing more to write about – but in this case we want more information about the document, for example the user name.
So to list out the subdirectories and their files, we use the LIST command and again we override the output to a file. This command can give us all of the information we need for this exercise. There is a catch though. The output of this command lists the directory on one line, and all the contents therein on subsequent lines. In addition to that, the position of the data is dependent on how long the user name, of the longest user name for that directory, is. This means that the position (and length) of the “virtual” columns of one directory, does not have the same position as that of another sub-directory.
So let’s wrap the above in a little CL program and our parameters are as follows;
&AttchPath = /runtime/
&DocName = TOC10F
&PgmError = 00
/************************************************** ******************/
/* SYSTEM - Systems */
/* AUTHOR - Deon A von Blerk (EcofIT Ltd) */
/* DATE - 25/01/09 */
/* Description - List the IFS files for a directory tree */
/* */
/* MODIFICATION HISTORY */
/* ==================== */
/* Date Nr By/Reason */
/* ---- --- --------- */
/************************************************** ******************/
PGM PARM(&AttchPath &DocName &PgmError)
DCL VAR(&AttchPath) TYPE(*CHAR) LEN(64)
DCL VAR(&DocName) TYPE(*CHAR) LEN(32)
DCL VAR(&QSHSTMT) TYPE(*CHAR) LEN(200)
DCL VAR(&Exists) TYPE(*CHAR) LEN(2)
DCL VAR(&PgmError) TYPE(*CHAR) LEN(2)
MONMSG MSGID(CPF0000 QSH0000) EXEC(GOTO CMDLBL(FAIL))
/************************************************** ******************/
/* Create the workfiles */
/************************************************** ******************/
DLTF FILE(QTEMP/QSHLIST)
MONMSG MSGID(CPF0000)
CRTPF FILE(QTEMP/QSHLIST) RCDLEN(200)
DLTF FILE(QTEMP/QSHFIND)
MONMSG MSGID(CPF0000)
CRTPF FILE(QTEMP/QSHFIND) RCDLEN(200)
/************************************************** ******************/
/* Fill the data */
/************************************************** ******************/
OVRDBF FILE(STDOUT) TOFILE(QTEMP/QSHLIST) OVRSCOPE(*JOB)
CHGVAR VAR(&QSHSTMT) VALUE('ls -lRT' *BCAT &AttchPath)
QSH CMD(&QSHSTMT)
DLTOVR FILE(STDOUT) LVL(*JOB)
OVRDBF FILE(STDOUT) TOFILE(QTEMP/QSHFIND) OVRSCOPE(*JOB)
CHGVAR VAR(&QSHSTMT) VALUE('find' *BCAT &AttchPath *TCAT '*')
QSH CMD(&QSHSTMT)
DLTOVR FILE(STDOUT) LVL(*JOB)
/************************************************** ******************/
/* Now check if the document exists */
/************************************************** ******************/
CALLPRC PRC(IFS50R) PARM(&DocName) RTNVAL(&PgmError)
GOTO CMDLBL(PERFECT)
/************************************************** ******************/
/* Fail */
/************************************************** ******************/
FAIL:
DLTF FILE(QTEMP/QSHLIST)
MONMSG MSGID(CPF0000)
DLTF FILE(QTEMP/QSHFIND)
MONMSG MSGID(CPF0000)
CHGVAR VAR(&PgmError) VALUE('01')
/************************************************** ******************/
/* EOJ */
/************************************************** ******************/
PERFECT:
ENDPGM
So we now have 2 flat files; one of which (QSHFind) is straightforward and easy to work with, the other (QSHList) is not quite what we want. As we can see from the picture below, each directory content row is aligned to the longest user name in that directory. But we do have one constant character in the row – the ‘:’ character in the time delimiter. Other important information (for this exercise) in the row is the ‘1’ or ‘2’ in column 13 where 1 = file and 2 = sub-directory.
191
**click to enlarge
We now need a little SQLRPGLE service program to display the results. The results can be done a couple of ways; straightforward SQL SELECT, with views, or using a UDTF. I’ve chosen the former, mainly for the reason that I’m working over QTEMP.
Declare QSHDocs_csr cursor for
with ListOut as
(select
locate(':', QSHList, 51) -1 as Offset,
QSHList
from QSHList
where substr(QSHList, 13, 1) = '1'
)
select distinct QSHFind,
substr(QSHList, 47+Offset-5, 12) as DateTime,
substr(QSHList, 55+Offset) as Document,
substr(QSHList, 15, Offset+1) as Owner
from QSHFind, ListOut
where QSHFind like '%' concat trim(:DocName) concat '%'
and substr(QSHList, 55+Offset)
like '%' concat trim(:DocName) concat '%';
This will then produce the list depicted below. You will note that I additionally select the QSHFind field as this is simple to store as a hidden field in the subfile which can then be opened directly from your display application. This way, we do not have to build a path and string bits and pieces together to enable the user to inspect or change the document.
192
**click to enlarge
Food for thought
This method can be used instead of the old-fashioned way of parsing for many of the IBM commands where the output options are only * or *PRINT i.e. no *OUTFILE option.
SQL is capable of using inline calculations in expressions (as in the “where” clause above).
In the CL, you could use more comprehensive message handling, including the use of environment variables; QIBM_QSH_CMD_OUTPUT and QIBM_QSH_CMD_ESCAPE_MSG. This obviously depends on the requirement of your application and or shop standards.
If you wish to comment on this article, you can do so at http://www.support.ecofitonline.com/forumdisplay.php?28-Articles (http://www.support.ecofitonline.com/forumdisplay.php?28-Articles)
About the Author:
Kit von Blerk is CEO of EcofIT Limited, an IBM i (AS/400, iSeries, System I, …) Software Solutions provider based in Germany. He started programming in COBOL (on an ICL ME29) in 1983 and changed to RPGIII on an IBM S/38 in 1986.
Listing a Directory Tree Contents
Kit von Blerk
My task recently was to attach x number of attachments to an email. “Piece of cake”, I hear you say. Allow me to explain the challenge then.
The challenge here is that the IFS path as well as the extension of the document e.g. .PDF, .DOCX, .XLS, etc. (we will call them files for the duration of this article), is not known at the time the user chooses which file should be attached to which outgoing document type e.g. order confirmation, delivery note, invoice, etc. (we will call them documents for the duration of this article).
In this TechTip, we will see that with just a few simple commands, one can obtain a wealth of information about the IFS file(s) to present to the user. But before we start, let me add: Yes, I know we could find a file in a less complicated way, and yes, I know we could make it more elegant as well – but I use this as an example only. Please note that this TechTip is written for v5r2.
What we have
The user currently has the facility to add (header and/or footer) text to outgoing documents which also includes a language delimiter and is defined by a 5 character Code field. Any number of Text Codes could be added to a document. A choice of text language is also available.
So for the purpose of this exercise, we’ll assume the following:
Text Code = TOC
Year = 10
Language = F
So, your IFS structure might look something similar to picture below, with these 6 files occurring in each sub-directory of the Attachments directory.
190
**click to enlarge
What we want
Let’s say the user wants to add the latest Terms and Conditions (TOC) to a document. But the TOC for 2009 is different to those for 2010, and the TOC for USA is different to those for Germany, and the TOC for customer A is different to those for customer B… and so on.
Let’s do it
Now let’s get the tree structure. To keep it simple, we’ll use the FIND QShell command and override the STDOUT output to a file.
If we wanted just the directory, it would be simple from there on - and I would have nothing more to write about – but in this case we want more information about the document, for example the user name.
So to list out the subdirectories and their files, we use the LIST command and again we override the output to a file. This command can give us all of the information we need for this exercise. There is a catch though. The output of this command lists the directory on one line, and all the contents therein on subsequent lines. In addition to that, the position of the data is dependent on how long the user name, of the longest user name for that directory, is. This means that the position (and length) of the “virtual” columns of one directory, does not have the same position as that of another sub-directory.
So let’s wrap the above in a little CL program and our parameters are as follows;
&AttchPath = /runtime/
&DocName = TOC10F
&PgmError = 00
/************************************************** ******************/
/* SYSTEM - Systems */
/* AUTHOR - Deon A von Blerk (EcofIT Ltd) */
/* DATE - 25/01/09 */
/* Description - List the IFS files for a directory tree */
/* */
/* MODIFICATION HISTORY */
/* ==================== */
/* Date Nr By/Reason */
/* ---- --- --------- */
/************************************************** ******************/
PGM PARM(&AttchPath &DocName &PgmError)
DCL VAR(&AttchPath) TYPE(*CHAR) LEN(64)
DCL VAR(&DocName) TYPE(*CHAR) LEN(32)
DCL VAR(&QSHSTMT) TYPE(*CHAR) LEN(200)
DCL VAR(&Exists) TYPE(*CHAR) LEN(2)
DCL VAR(&PgmError) TYPE(*CHAR) LEN(2)
MONMSG MSGID(CPF0000 QSH0000) EXEC(GOTO CMDLBL(FAIL))
/************************************************** ******************/
/* Create the workfiles */
/************************************************** ******************/
DLTF FILE(QTEMP/QSHLIST)
MONMSG MSGID(CPF0000)
CRTPF FILE(QTEMP/QSHLIST) RCDLEN(200)
DLTF FILE(QTEMP/QSHFIND)
MONMSG MSGID(CPF0000)
CRTPF FILE(QTEMP/QSHFIND) RCDLEN(200)
/************************************************** ******************/
/* Fill the data */
/************************************************** ******************/
OVRDBF FILE(STDOUT) TOFILE(QTEMP/QSHLIST) OVRSCOPE(*JOB)
CHGVAR VAR(&QSHSTMT) VALUE('ls -lRT' *BCAT &AttchPath)
QSH CMD(&QSHSTMT)
DLTOVR FILE(STDOUT) LVL(*JOB)
OVRDBF FILE(STDOUT) TOFILE(QTEMP/QSHFIND) OVRSCOPE(*JOB)
CHGVAR VAR(&QSHSTMT) VALUE('find' *BCAT &AttchPath *TCAT '*')
QSH CMD(&QSHSTMT)
DLTOVR FILE(STDOUT) LVL(*JOB)
/************************************************** ******************/
/* Now check if the document exists */
/************************************************** ******************/
CALLPRC PRC(IFS50R) PARM(&DocName) RTNVAL(&PgmError)
GOTO CMDLBL(PERFECT)
/************************************************** ******************/
/* Fail */
/************************************************** ******************/
FAIL:
DLTF FILE(QTEMP/QSHLIST)
MONMSG MSGID(CPF0000)
DLTF FILE(QTEMP/QSHFIND)
MONMSG MSGID(CPF0000)
CHGVAR VAR(&PgmError) VALUE('01')
/************************************************** ******************/
/* EOJ */
/************************************************** ******************/
PERFECT:
ENDPGM
So we now have 2 flat files; one of which (QSHFind) is straightforward and easy to work with, the other (QSHList) is not quite what we want. As we can see from the picture below, each directory content row is aligned to the longest user name in that directory. But we do have one constant character in the row – the ‘:’ character in the time delimiter. Other important information (for this exercise) in the row is the ‘1’ or ‘2’ in column 13 where 1 = file and 2 = sub-directory.
191
**click to enlarge
We now need a little SQLRPGLE service program to display the results. The results can be done a couple of ways; straightforward SQL SELECT, with views, or using a UDTF. I’ve chosen the former, mainly for the reason that I’m working over QTEMP.
Declare QSHDocs_csr cursor for
with ListOut as
(select
locate(':', QSHList, 51) -1 as Offset,
QSHList
from QSHList
where substr(QSHList, 13, 1) = '1'
)
select distinct QSHFind,
substr(QSHList, 47+Offset-5, 12) as DateTime,
substr(QSHList, 55+Offset) as Document,
substr(QSHList, 15, Offset+1) as Owner
from QSHFind, ListOut
where QSHFind like '%' concat trim(:DocName) concat '%'
and substr(QSHList, 55+Offset)
like '%' concat trim(:DocName) concat '%';
This will then produce the list depicted below. You will note that I additionally select the QSHFind field as this is simple to store as a hidden field in the subfile which can then be opened directly from your display application. This way, we do not have to build a path and string bits and pieces together to enable the user to inspect or change the document.
192
**click to enlarge
Food for thought
This method can be used instead of the old-fashioned way of parsing for many of the IBM commands where the output options are only * or *PRINT i.e. no *OUTFILE option.
SQL is capable of using inline calculations in expressions (as in the “where” clause above).
In the CL, you could use more comprehensive message handling, including the use of environment variables; QIBM_QSH_CMD_OUTPUT and QIBM_QSH_CMD_ESCAPE_MSG. This obviously depends on the requirement of your application and or shop standards.
If you wish to comment on this article, you can do so at http://www.support.ecofitonline.com/forumdisplay.php?28-Articles (http://www.support.ecofitonline.com/forumdisplay.php?28-Articles)
About the Author:
Kit von Blerk is CEO of EcofIT Limited, an IBM i (AS/400, iSeries, System I, …) Software Solutions provider based in Germany. He started programming in COBOL (on an ICL ME29) in 1983 and changed to RPGIII on an IBM S/38 in 1986.