Wednesday, December 23, 2015

Searching for strings in source files

find string pdm fndstrpdm

I was recently asked how it is possible to search all the source members in QCLSRC to find which program called a particular Cobol program. I am sure most of you reading this will immediately shout "Find string PDM!", and you are right.

I thought that this would be a good excuse to explain the Find String PDM command, FNDSTRPDM, to those who are not familiar with it. And I will then give an example CL program to show of how I could search one or all of the source files in a library, which is really a reminder of things I have written about before: SQL, do loop, subroutine, changing name of spool files.

I know that there are third party tools that do this too. I have used several of them, but I am not going to mention them here. This post is going to just use the native IBM i features and commands.

I think most developers are aware that if you want to scan a source member or members you can just go into PDM and put '25', "Find string", next to the members you want search, and press Enter. You can do the same using the FNDSTRPDM command, see below. In this example I want to print a list of members with the string "cobol" in them. In the command prompts I have entered my input in lower case so you can see what is mine and what is the command's default values.

                        Find String Using PDM (FNDSTRPDM)

 Find 'string'  . . . . . . . . . STRING       cobol                          
                                                                                
                     
 File . . . . . . . . . . . . . . FILE         mysrc     
   Library  . . . . . . . . . . .              *LIBL     
 Member . . . . . . . . . . . . . MBR          *all      
                           + for more values             
 Operation to perform:            OPTION
   Option . . . . . . . . . . . .              *none   
   Prompt . . . . . . . . . . . .              *NOPROMPT

                            Additional Parameters

 Columns to search:               COL
   From column  . . . . . . . . .              1      
   To column  . . . . . . . . . .              *RCDLEN
 Kind of match  . . . . . . . . . CASE         *IGNORE
 Print list . . . . . . . . . . . PRTMBRLIST   *yes
 Print records:                   PRTRCDS
   Number to find . . . . . . . .              *NONE
   Print format . . . . . . . . .                     
   Mark record  . . . . . . . . .                     
   Record overflow  . . . . . . .                       
 Parameters . . . . . . . . . . . PARM                                        

When I press Enter a spool file is created, QPUOPRTF, that contains the members that match the selection criteria I chose.

Having given the person who asked this question the answer, use FNDSTRPDM, I began to think how I could make it easier to use. I would want to have the option to search all the source files in a library for a string, and be able to easily distinguish which spool file came from which source file. I could write a fairly simple CL program that would include the following that I written about before:

The "top" of the program looks very straight forward. There are four parameters that would need to be passed to this program:

01  PGM PARM(&STRING &SRCFILE &SRCFLIB &RTNCDE)

02  DCL VAR(&STRING) TYPE(*CHAR) LEN(30)
03  DCL VAR(&SRCFILE) TYPE(*CHAR) LEN(10)
04  DCL VAR(&SRCFLIB) TYPE(*CHAR) LEN(10)
05  DCL VAR(&RTNCDE) TYPE(*CHAR) LEN(1)
06  DCL VAR(&SQL) TYPE(*CHAR) LEN(200)
07  DCL VAR(&LOOP) TYPE(*LGL) VALUE('1')

08  DCLF FILE(QTEMP/WORKFILE)

09  CHGVAR VAR(&RTNCDE) VALUE(' ')
  1. &STRING - Search string
  2. &SRCFILE - If only one source file to be searched then will contain the source file name. If I want to search all source files in a library then will contain "*ALL"
  3. &SRCFLIB - The library containing the source file(s)
  4. &RTNCDE - This is the return code passed back to whatever call this program. If it is not blank then an error was encountered

The next step is to validate that the library name entered does exist. This is simply done using the Check Object command, CHKOBJ, is used on line 10. If the library does not exist the program ends and returns a value of '1'.

10  CHKOBJ OBJ(&SRCFLIB) OBJTYPE(*LIB)
11  MONMSG MSGID(CPF0001 CPF9801) EXEC(DO)
12    CHGVAR VAR(&RTNCDE) VALUE('1')
13    RETURN
14  ENDDO

Then I need to validate the source file name, if a name and not "*ALL was passed. The CHKOBJ command is used again. If the a file of the passed name is not found the program ends and returns a value of '2', lines 18 – 19.

15  IF COND(&SRCFILE *NE '*ALL') THEN(DO)
16    CHKOBJ OBJ(&SRCFLIB/&SRCFILE) OBJTYPE(*FILE)
17    MONMSG MSGID(CPF0001 CPF9801) EXEC(DO)
18      CHGVAR VAR(&RTNCDE) VALUE('2')
19      RETURN
20    ENDDO

21    CALLSUBR SUBR(FINDSTRING)
22    RETURN
23  ENDDO

If the file was found, the subroutine FINDSTRING is called, line 21, and then the program ends.

What remains is what to do if "*ALL" has been entered as the source file. What I am going to do is to make a work file that contains all of the source files in the source file library, and then read the work file.

I am going to use SQL to create a table and extract the data from the SQL View SYSTABLES into this table, called WORKFILE.

24  RUNSQL SQL('DROP TABLE QTEMP.WORKFILE') +
             COMMIT(*NONE) NAMING(*SQL)
25  MONMSG MSGID(SQL9010)

26  CHGVAR VAR(&SQL) +
             VALUE('CREATE TABLE QTEMP.WORKFILE AS +
                   (SELECT SYSTEM_TABLE_NAME,+
                           SYSTEM_TABLE_SCHEMA,+
                           FILE_TYPE +
                      FROM QSYS2.SYSTABLES +
                      WHERE FILE_TYPE = ''S'' +
                        AND SYSTEM_TABLE_SCHEMA = ''' +
                            || &SRCFLIB |< ''') +
                    WITH DATA')

27  RUNSQL SQL(&SQL) COMMIT(*NONE) NAMING(*SQL)

Line 24: I always like to try and delete a work file/table to make sure it is gone before I try and build a new one. SQL's DROP TABLE is the equivalent of DTLF. I have also chosen to use the SQL naming convention, therefore, the separator between the library and file is a period/full stop/dot (depending on where you are in the world).

Line 26: As I cannot use substitution variables in the Run SQL command, RUNSQL, I am building my SQL statement in a variable, &SQL, where I can insert my source library name into the WHERE clause.

Line 27: The statement is executed. I have not monitored this line because if it fails I want to know about it.

Having created the work file now I need to read it.

28  DOWHILE    COND(&LOOP)
29    RCVF
30    MONMSG MSGID(CPF0000) EXEC(LEAVE)

31    CHGVAR VAR(&SRCFILE) VALUE(&SYS_TNAME)
32    CHGVAR VAR(&SRCFLIB) VALUE(&SYS_DNAME)
33    CALLSUBR SUBR(FINDSTRING)
34  ENDDO

Line 28: For those of you not familiar with the Do loop in CL I have to condition it with a logical variable, which is defined on line 7.

Line 29: Work file is read.

Lines 31 and 32: Contents of the file fields are moved into the variables that are used in the subroutine.

Line 33: Subroutine is called.

The subroutine is simple it just contains two commands:

36  SUBR SUBR(FINDSTRING)
37    OVRPRTF FILE(QPUOPRTF) USRDTA(&SRCFLIB) +
                SPLFNAME(&SRCFILE) OVRSCOPE(*CALLLVL)

38    FNDSTRPDM STRING(&STRING) +
                  FILE(&SRCFLIB/&SRCFILE) +
                  MBR(*ALL) OPTION(*NONE) +
                  PRTMBRLIST(*YES)
39  ENDSUBR

Line 37: The first command overrides the printer file name so that the spool file name is the name of the source file and the user data is the source file library name.

Line 38: Here it is, finally, the FNDSTRPDM command.

I ran the program with the following parameters:

  CALL FINDSTRING ('COBOL' '*ALL' 'MYLIB' &RTNCDE)

And four spool files were produced, each name after the source file:

                    Work with All Spooled Files

                              Device or                     Total
 Opt  File        User        Queue       User Data   Sts   Pages
  _   DEMOSRC     SIMON       MYOUTQ      MYLIB       RDY       1
  _   DEVSRC      SIMON       MYOUTQ      MYLIB       RDY       2
  _   EXAMPLES    SIMON       MYOUTQ      MYLIB       RDY       2
  _   OLDSRC      SIMON       MYOUTQ      MYLIB       RDY       2

To be accurate you have to be sure that the source files match the objects. If they do not you can misleading results.

Having shown the program in parts this is what it looks like in its entirety:

01  PGM PARM(&STRING &SRCFILE &SRCFLIB &RTNCDE)

02  DCL VAR(&STRING) TYPE(*CHAR) LEN(30)
03  DCL VAR(&SRCFILE) TYPE(*CHAR) LEN(10)
04  DCL VAR(&SRCFLIB) TYPE(*CHAR) LEN(10)
05  DCL VAR(&RTNCDE) TYPE(*CHAR) LEN(1)
06  DCL VAR(&SQL) TYPE(*CHAR) LEN(200)
07  DCL VAR(&LOOP) TYPE(*LGL) VALUE('1')

08  DCLF FILE(QTEMP/WORKFILE)

09  CHGVAR VAR(&RTNCDE) VALUE(' ')

10  CHKOBJ OBJ(&SRCFLIB) OBJTYPE(*LIB)
11  MONMSG MSGID(CPF0001 CPF9801) EXEC(DO)
12    CHGVAR VAR(&RTNCDE) VALUE('1')
13    RETURN
14  ENDDO

15  IF COND(&SRCFILE *NE '*ALL') THEN(DO)
16    CHKOBJ OBJ(&SRCFLIB/&SRCFILE) OBJTYPE(*FILE)
17    MONMSG MSGID(CPF0001 CPF9801) EXEC(DO)
18      CHGVAR VAR(&RTNCDE) VALUE('2')
19      RETURN
20    ENDDO

21    CALLSUBR SUBR(FINDSTRING)
22    RETURN
23  ENDDO

24  RUNSQL SQL('DROP TABLE QTEMP.WORKFILE') +
             COMMIT(*NONE) NAMING(*SQL)
25  MONMSG MSGID(SQL9010)

26  CHGVAR VAR(&SQL) +
             VALUE('CREATE TABLE QTEMP.WORKFILE AS +
                   (SELECT SYSTEM_TABLE_NAME,+
                           SYSTEM_TABLE_SCHEMA,+
                           FILE_TYPE +
                      FROM QSYS2.SYSTABLES +
                      WHERE FILE_TYPE = ''S'' +
                        AND SYSTEM_TABLE_SCHEMA = ''' +
                            || &SRCFLIB |< ''') +
                    WITH DATA')

27  RUNSQL SQL(&SQL) COMMIT(*NONE) NAMING(*SQL)

28  DOWHILE    COND(&LOOP)
29    RCVF
30    MONMSG MSGID(CPF0000) EXEC(LEAVE)

31    CHGVAR VAR(&SRCFILE) VALUE(&SYS_TNAME)
32    CHGVAR VAR(&SRCFLIB) VALUE(&SYS_DNAME)
33    CALLSUBR SUBR(FINDSTRING)
34  ENDDO

35  RUNSQL SQL('DROP TABLE QTEMP.WORKFILE') +
             COMMIT(*NONE) NAMING(*SQL)
/*====================================================*/
36  SUBR SUBR(FINDSTRING)
37    OVRPRTF FILE(QPUOPRTF) USRDTA(&SRCFLIB) +
                SPLFNAME(&SRCFILE) OVRSCOPE(*CALLLVL)

38    FNDSTRPDM STRING(&STRING) +
                  FILE(&SRCFLIB/&SRCFILE) +
                  MBR(*ALL) OPTION(*NONE) +
                  PRTMBRLIST(*YES)
39  ENDSUBR
/*====================================================*/
40  ENDPGM

 

You can learn more about the FNDSTRPDM command from the IBM website here.

 

This article was written for IBM i 7.2, and should work for earlier releases too.

9 comments:

  1. Another great tool for our tool kit, thanks Simon.

    ReplyDelete
  2. Thank you , I am trying to implement it similarly but definitely very good post

    ReplyDelete
  3. what is CPF0001 and how do we get SQL9010 monmsg for the various SQL commands run .
    For CL commands and its CPFXXXX, we can very well get from the IBM website or F1 on the command. - J

    ReplyDelete
    Replies
    1. For any error message you can use the following command to see the message text for it: DSPMSGD RANGE(CPF0001) MSGF(QCPFMSG)

      For messages with that start with 'SQL' you can find the text using the same command as above, bit with a different message file: DSPMSGD RANGE(SQL9001) MSGF(QSQLMSG)

      If you really want to get to the root of what the SQL error is then I recommend you add GET DIAGNOSTICS SQL statements, see here.

      Or add code to check the SQLCOD field after every SQL statement you will have to go to IBM's KnowledgeCenter for their meaning, see here. You will probably have to look in the SQLCA data structure for more information too.

      Delete
    2. Thank you Simon - J

      Delete
  4. Hi Simon, thanks for the great article; I am using it, but, is there any way that in the procedure that I could generate a file instead of a report.

    ReplyDelete
    Replies
    1. FNDSTRPDM will only produce a spool file.

      I guess it would depend upon what information you want in your file. You could just take the spool file, copy it to the IFS as a TXT and use that as your file.

      Or you could write your own tool, trust me it is not that difficult, to scan source members looking for a string, and then you have total control of what you want as output.

      Delete
  5. Hi, First thank you.
    I have on error with CL Compilation: &SYS_TNAME and &SYS_DNAME are not defined

    Thank you.

    ReplyDelete
    Replies
    1. &SYS_TNAME and &SYS_DNAME are from the created table QTEMP/WORKFILE.

      If you have created QTEMP/WORKFILE before you compile then the definition of these two variables will be found by the compiler as WORKFILE in defined in the DCLF command.

      Delete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.