|
The Ins and Outs of Qshell
by Ted Holt
Hello, Qshell fans! In the last lesson,
you learned about stream I/O. If you have not read that article, it would be
good for you to do so now, because some of the concepts mentioned in this article were explained there. In
today's lesson, you're going to learn about some basic input and output commands you can use in Qshell
scripts. Soon you'll be quite the authority on using stream files in Qshell scripts.
Read
Qshell has only one utility for reading stream files, cleverly named read. The read utility reads a
line of data from a file and assigns the data to one or more variables. The default input file is standard input
(stdin), but you can also read from other files using redirection or file descriptors. (This was discussed in
the previous
article.)
The data is usually text data, not binary data. That is, you get everything up to an end-of-line character
(usually the carriage return and linefeed sequence). If a line ends with a backslash (\) followed immediately
by the end-of-line sequence, Qshell assumes that the input data is continued on the next record of input.
However, if you use the -r option, the backslash is not treated as a continuation character.
The following file contains four records, but Qshell will read them as three records if the -r option is not
specified:
This is one line of input.
This is \
another line of input.
This is the third line of input.
The word read may be followed by options or variable names. Let's consider variable names first.
When the read utility reads a record, it separates the contents of the record into fields. The value in the IFS
(internal field separator) variable tells the read utility how to separate the data. Normally, IFS is set to a
binary zero, which means that fields are separated by white space--one or more blanks, tabs, or newline
characters.
Qshell fills in the variables with the field values until it runs out of data or variables. If there are more data
values than variables, all remaining data values are placed in the last variable.
Examine the following script fragment. Notice that the IFS variable is set to a comma. This script reads a
CSV (comma-separated values) file:
IFS=','
read name age phone
Assume this input data:
Bill,11,1-888-SHOP-IBM
After the read finishes, the variable name is Bill, the variable age is 11, and the variable
phone is 1-888-SHOP-IBM.
Qshell permits three options. (Other Unix shells permit more.) I have already mentioned the -r option,
which disables the backslash line-continuation character. There is also option -u, which makes Qshell read
from a file that was opened with a file descriptor. (I discussed file descriptors in the last article.)
The other option is -p, which allows a prompt string to be displayed on standard error. Here's an example:
read -p "Please enter your name:" name
I don't recommend that you use this option, because the prompt only displays when the script is run with
the dot operator, and the dot operator is not the way you will usually want to run Qshell scripts.
Echo
While there is only one input utility, there are several Qshell output utilities. The easiest (and least
powerful) one is echo, which sends one or more arguments, followed by an end-of-line sequence, to
standard output (stdout).
The word echo can be followed by one or more arguments separated by white space. An argument
can be a quoted string, an unquoted string, or a variable. Quoted strings may be delimited with single (') or
double (") quotation marks. Single quotation marks are "strong" quotes, and don't allow the system to
substitute values for variables inside the quoted strings. (See "Working with Parameters and
Variables in Qshell Scripts" for more about single and double quotation marks.)
Suppose this echo command runs with the variables defined as in the preceding section:
echo "$name is $age years old."
Here's the output:
Bill is 11 years old.
Print
Print is echo, but with more muscle. Print beats echo in two ways: Print allows options, and it can interpret
control commands within the arguments it sends to output files.
There are three allowable options: -n, -r (or -R), and -u. The -n option tells print not to advance to a
new line after printing the arguments. In the following example, both print statements print to one line of
output:
print -n "Hello "
print $name
If the name variable has the value Bill, this is the output:
Hello Bill
The -u option tells print to send output to the file opened under a certain file descriptor.
The -r (or -R) option tells print not to interpret the control characters in the arguments.
The control characters begin with a backslash and are listed in the following table:
|
Character
|
Description
|
|
\a
|
sound the terminal's alarm
|
|
\b
|
backspace one character
|
|
\c
|
ignore subsequent arguments and suppress
newline
|
|
\f
|
formfeed (clears the screen)
|
|
\n
|
newline (carriage return and linefeed)
|
|
\r
|
return (carriage return, but no linefeed;
returns to
beginning of the line)
|
|
\t
|
tab
|
|
\v
|
vertical tab (linefeed, but no carriage
return; moves
cursor down)
|
|
\0x
|
EBCDIC character; x is a
1-, 2-,
or 3-digit decimal number
|
Here are some examples of print and the output it produces:
|
Command
|
Output
|
|
print "a\bc"
|
c
|
|
print "a\tb"
|
a
b
|
|
print "a\nb"
|
a
b
|
|
print "a\vb"
|
a
b
|
|
print "abcde\rfgh"
|
fghde
|
To include a backslash in a string, you normally code two backslashes, although one backslash if often
sufficient. However, if the character following the backslash is a control character, you will have to code
three backslashes to print one. Consider this example:
print "Look in directory c:\\\temp\\mystuff\\zipfiles."
Qshell produces this output:
Look in directory c:\temp\mystuff\zipfiles.
If I leave only two backslashes after the colon, Qshell interprets the \t as a tab and drops the backslash that
follows the colon. The Qshell manual does not tell me why this behavior occurs.
If you code a single backslash before any character other than the ones mentioned above, the behavior is
undefined and unpredictable. You may get a backslash in the output, and, then again, you may not.
Printf
The printf utility is based on C's printf function and differs from Qshell's print utility in several ways. First,
printf provides a way to format the way variables are printed. Second, printf does not automatically
generate an end-of-line sequence when it writes. If you want to advance to a new line, you must include a
\n control sequence in the format string. Third, printf does not support the print options. For instance, you
cannot use the -u option to direct print to a certain file; you will have to use a file descriptor with
redirection operators.
You must provide at least one argument--a format string--to printf. The format string may contain plain
text, control characters, and special formatting sequences.
Printf supports the control sequences that print supports. That is, you can use \t to tab, \b to backspace, and
so on. I just said it, but it bears repeating: The printf utility does not linefeed after printing unless there is a
\n sequence in the format string.
A formatting sequence has the following syntax:
%[flags][width][.precision]conversion
The brackets ([]) indicate optional portions and are not coded.
Since the conversion character is the only required portion of a formatting sequence, let's look at it first.
You must include a conversion character to tell printf how to format an argument:
|
Character
|
Description
|
Acceptable data types
|
|
c
|
unsigned character
|
Character
|
|
d
|
signed decimal number
|
Integer
|
|
e, E
|
scientific notation
|
Real
|
|
f
|
print as a real number
|
Real
|
|
g, G
|
scientific notation with significant
digits
|
Real
|
|
i
|
signed decimal number (same as d?)
|
Integer
|
|
o
|
unsigned octal number
|
Integer
|
|
s
|
string
|
Character
|
|
u
|
unsigned decimal number
|
Integer
|
|
x
|
unsigned hexadecimal number with lowercase
a-f
|
Integer
|
|
X
|
unsigned hexadecimal number with uppercase
A-F
|
Integer
|
For example, %d means that a number is to be printed in decimal format, while %o means a number is to
be printed in octal format.
Printf applies the other arguments to the conversion characters in the format string. If printf finds more
arguments than conversion characters, it reuses the format string as often as necessary. For example, if
there are two conversion characters in the formatting string, but four arguments following the conversion
string, printf will use the formatting string twice.
Now is also a good time to mention how to print a percent sign (%). Since the percent sign is used to
indicate the beginning of a formatting sequence, you must double any percent signs to be printed.
Here are some examples. Assume that name is Bill and age is 11.
|
Command
|
Output
|
|
printf "%c\n" $name
|
B
|
|
printf "%s\n" $name
|
Bill
|
|
printf "%d\n" $age
|
11
|
|
printf "%i\n" $age
|
11
|
|
printf "%o\n" $age
|
13
|
|
printf "%x\n" $age
|
b
|
|
printf "%X\n" $age
|
B
 
;
|
|
printf "%f\n" $age
|
11.000000
|
|
printf "%e\n" $age
|
1.100000e+01
|
|
printf "%g\n" $age
|
11
|
|
printf "%s is %d years old.\n"
$name $age
|
Bill is 11 years old.
|
|
printf "%d%%\n" 25
|
25%
|
|
printf "%d %d\n" 15 16 20 22
|
15 16
20 22
|
Notice the double percent sign in the next-to-last example and the reused format string in the last example.
You may use the optional portions of the formatting sequence--flags, width, and precision--to further
modify the appearance of the output.
The five flags are used only with numeric values. You may use more than one of them in a formatting
sequence. Here are the flags and their meanings:
|
Flag
|
Description
|
|
space
|
Precede positive values with space, negative
values
with -.
|
|
+
|
Precede positive values with +, negative
values with
-.
|
|
-
|
Left justify the output.
|
|
0
|
Display leading zeros.
|
|
#
|
Precede octal numbers with 0.
Precede hexadecimal numbers with
0x or 0X.
For real numbers, display the decimal point.
For g or G, display trailing zeros.
|
The width is the minimum number of characters to be sent to output. If you code an asterisk (*), the width
is taken from the next argument:
|
Command
|
Output
|
Comment
|
|
printf "%5i\n" $age
|
11
|
There are three leading blanks.
|
|
printf "%0*d\n" 4 7
|
0007
|
The length is 4; the value is 7.
|
The function of the precision depends on the conversion character:
|
Character
|
Description
|
|
d,i,o,u,x,X
|
minimum number of digits to be
displayed
|
|
e, E, f
|
number of decimal digits to be
displayed
|
|
g, G
|
maximum number of significant digits
|
|
s
|
maximum number of characters to be
displayed
|
A technique you might find handy is to use printf to format variables. Here's an example:
amount=$(printf "$%-10.2f" $price)
If price has a value 245.3, amount takes the value $245.30, followed by four blanks, for a total length of 11.
Let's see why.
Look at the format string in the printf command. The f means that the value is to be printed as a
floating point (real) number. The 10 refers to the overall length of the number, including sign and
decimal positions. The 2 means that two decimal positions are to be shown. The hyphen (-) means
that the number is left-adjusted within the 10 positions. When the dollar sign ($), which is not part of the
formatting sequence, is placed in front of the number, the total length is 11.
To load the output of printf into the variable called amount, use command substitution, a technique
whereby the output of a command is substituted for the command itself. To tell Qshell to use command
substitution, enclose the command within parentheses and precede it with a dollar sign.
Dspmsg
Another way to send output to stdout is with the display message (dspmsg) utility. This feature was
designed to make it easier to use scripts with different national languages. Instead of hard-coding messages
in your scripts, you store messages in a message catalog and use dspmsg to retrieve them. If you decide to
run your scripts in an environment where people use another language, you translate the messages without
having to modify the script.
You'll need a message catalog to hold the messages. Begin by creating the messages in a source physical
file member. Here's an example:
$ My message catalog
$quote '
$set 1
1 'File %s not found.\n'
2 'File is a directory.\n'
$set 2
1 'Directory not found.\n'
2 'File is not a directory.\n'
The first line is a comment because it begins with a dollar sign and a space. The second line says that the
single quote is used a quotation character to delimit the message text. The third line indicates that the
following messages are in set 1. The two lines following define messages 1 and 2 of the set. The last three
lines define two messages in set 2.
To create or update the message catalog, use either the Generate Message Catalog (GENCAT) or the Merge
Message Catalog (MRGMSGCLG) CL command. Both commands are identical in function. The following
command creates a message catalog called mymsgcat in the current directory from the source in member
MYMSGCAT in MYLIB/MYSRC:
GENCAT +
CLGFILE(mymsgcat) +
SRCFILE('/qsys.lib/mylib.lib/mysrc.file/mymsgcat.mbr')
To display message 1 of set 2, use this command within a Qshell script:
dspmsg -s 2 mymsgcat 1
You may have noticed that message 1 of set 1 includes the substitution characters %s to indicate that a
parameter will be passed to the message. To indicate that file x is not found, send message 1 of set 1
with a parameter of x, like this:
dspmsg mymsgcat 1 'File %s was not found.\n' x
If Qshell locates the message, the following string of characters is sent to stdout:
File x not found.
If the message is not located, the string in between the quotes is sent to stdout instead.
To learn more about message catalogs, see the System
Planning and Installation manual.
The Ins and Outs
In the last issue, you learned about stream files. In this issue, you have learned how to gain flexibility when
reading and writing stream data. I hope you see that while the Qshell language isn't as robust as other
languages you may be familiar with, it should be adequate for your scripting needs.
|