Simple File Creation Example Cat & Variables Example Simple Echo Complex File Creation Example Complex Echo Forms Example List Example Counted List Example Sorted List File Reading Example Reverse List & Count Words Example Tail -f Option

Using Files

When creating scripts, it is often a requirement to generate some form of file for storing some temporary information or even something more permanent, like a log file. Sometimes you may even want to create another script for later execution. There are a number of ways to do this, some of which we have already met.

Simple File Creation:

There are two simple ways to create another file, one uses the cat command in conjunction with the redirect symbol, the other way is to use the echo command in conjunction with the redirect symbol. The example Indented Cat is a good example of the cat method in the Pipes and Redirects section. This example only contains litteral text however. It is more appropriate to see something like the example below, which shows a variable being used in the source data block.

Example cat and variables

cat >> $sql0 <<-EOA
	SET ECHO OFF
	SET FEEDBACK OFF
	SET HEADING OFF
	SELECT my_package.my_function($column)
		FROM v\$database
		WHERE name LIKE '%&1%';
	EXIT
EOA
sqlplus -s $uid/$password@database @$sql0 $sql_arg_1	> $log0

The file created has its name stored in the variable $sql0 and as we can see the block between the EOA flags is the data that goes into the file. The data block is actually a segment of SQL*Plus statements, as indicated by the filename variable. As is common with SQL*Plus code, the key words are picked out in ALL CAPS, with objects (tables, procedures, columns, etc.) all in lower case. The SELECT line contains a reference to a called, packaged, PL/SQL function which has a column name as an argument. Here the column name is held in a variable called $column and this will be substituted at script run-time by the real value.

There are some unfortunate consequences of generating SQL*Plus statements from within a shell script which you have to be aware of. Firstly, don't forget to put the EXIT statement at the end of the block or you will end up with a script that stays in SQL*Plus forever. Secondly, don't forget to put the semi-colons (;) at the end of every SQL statement, or each statement will overwrite the previous one or just create one long unprocessable mess. Thirdly, some internal database tables may contain the dollar symbol, which is special to the shell, so escape them with the back-slash (\) as shown on the FROM line.

On the WHERE line there is a reference to a SQL*Plus positional parameter '&1' which will pick up its value from the variable $sql_arg_1 at run-time as shown in the last line, just after the end of block flag. Did I say this was a simple example? Well, at least you don't have to worry about quoting when using this method. All quotes find their way to the destination file unscathed. Now to do the same thing using echo instead of cat, see the example below.

Example simple echo

echo "SET ECHO OFF"					>> $sql0
echo "SET FEEDBACK OFF"					>> $sql0
echo "SET HEADING OFF"					>> $sql0
echo "SELECT my_package.my_function($column)"		>> $sql0
echo "	FROM v\$database"				>> $sql0
echo "	WHERE name LIKE '%&1%';"			>> $sql0
echo "EXIT"						>> $sql0
sqlplus -s $uid/$password@database @$sql0 $sql_arg_1	>  $log0

Complex File Creation:

So what's the point of all this extra typing? Well for one thing it allows you to put special bits of code into the block which will only be used at certain times, by hiding them in complex command groups. This example shows how this is done below.

Example complex echo forms

echo "SET ECHO OFF"					>> $sql0
echo "SET FEEDBACK OFF"					>> $sql0
echo "SET HEADING OFF"					>> $sql0
echo "SELECT my_package.my_function($column)"		>> $sql0
echo "	FROM v\$database"				>> $sql0
if [ "$db_type" = "m" ]
then
    echo "	WHERE name = '$db_name';"		>> $sql0
else
    echo "	WHERE name LIKE '%&1%';"		>> $sql0
fi
echo "EXIT"						>> $sql0
sqlplus -s $uid/$password@database @$sql0 $sql_arg_1	>  $log0

This is basically the same block except the WHERE clause has been hidden inside an if statement. Now, depending on the Database Type in the $db_type variable, the WHERE clause can take one of two forms. Conveniently, the additional argument which is not required by SQL*Plus in the first form, is ignored at execution time, even though it is still available on the last line. This is common with all scripts, arguments are only used if they are referenced from within the script.

So there you have the first two ways of creating another file from a script. The version using cat can only cope with a single output form, the version using echo can output a multitude of forms depending on the complex command forms you use. The choice is yours. There are, however, other ways to create output files. You can use direct generation as in the example List to create a list of files. Or the indirect method shown in the example Counted List where lines are built inside a loop construct and then appended to the file to create a menu file. Or in the example Sorted List where a list of words is sorted into alphabetic order, duplicates are removed, then the rest stored in a file.

Example list

ls -1 *.log > $lst0

Example counted list

count=1
for file in `ls -1 *.log`
do
    echo "$count: $file"	>> $mnu0
    count=`expr $count + 1`
done

Example sorted list

echo $@ | tr ' ' '\n' | sort -u >  $lst0

File Reading:

There are also the head and tail commands which are useful when reading files as opposed to writing. We have already seen one example of their use in a previous section. Here are a few more. The following example Reverse List and Count Words reverses the order of lines in a file and adds a word count to the end of each line. Note the use of expr to change the type of the counter variable $file_length before the while command sees it.

Example reverse list and count words

file_length=`wc -l $input_file | cut -c1-8`		# Count Lines
file_length=`expr $file_length + 0`			# Change Type
while $file_length					# Start Loop
do
    line=`head -$file_length $input_file | tail -1`	# Get Line
    words=`s_count_args $line`				# Count Words
    echo "$line = $words words"	>> $output_file		# Write Line
    file_length=`expr $file_length - 1`			# Decrement
done

Don't forget that the tail command has a few more options up its sleave than most users remember. As well as the minus, there is the plus. This acts a bit like an inverse head command in that the command tail +10 -10 will give you lines 10 through to 10 from the end - in a 33 line file, that is from 10 through 23. There are also the l, b, and c options (lines, blocks, characters) which affect the element being counted by the plus and minus numbers. Then there is r which reverses the order of the lines returned. Lastly there is f which is often used as part of a monitor script. This option does not terminate, but just keeps looking at the end of the named file waiting for something else to be written. Here is an example of the tail -f option in use in use, which is a small section of a much longer script.

Example tail -f option

#----------------------------------------------------------------
# Launch FTP in background and collect return code on completion
#----------------------------------------------------------------
( ftp -nv < $ftp_commands > $ftp_log 2>&1 ; \
  echo $? > $ftp_return_code ) &
job_1_number=$!
tail -f $ftp_log | grep $search > $done_log &
job_2_number=$!
#-------------------------------
# Check done_log until complete
#-------------------------------
while [ `grep -c $search $done_log` = 0 ]
do
    sleep 1
done
#-----------------------------------------
# Tidy Up the jobs if hung and log result
#-----------------------------------------
if [ "`cat $ftp_return_code`" = "" ]
then
    kill -9 $job_1_number $job_2_number
    echo "FTP Complete at `s_date`"		   >> $process_log
else
    echo "FTP returned a [`cat $ftp_return_code`]" >> $process_log
    echo "Aborting $0 at `s_timestamp`"
    echo "========================================="
    cat $process_log
    echo "========================================="
    exit
fi

Note the use of $! to return the background job numbers for later termination. Also see how the two commands on the first line have been submitted to a subshell in the background and yet the return code from inside the subshell, being redirected to a file, is available for testing in the parent shell. The tail -f command will stay looking at the end of the log file for ever if left, hence the need for the tidy up later, but the grep $search only passes a known string on to the completion file $done_log. This can be checked in a loop with sleeps until something appears. Notice how flexible the test command is by allowing the output of a cat command to be used as part of a string comparison. Of course in a real script you might want to code in some loop counters to stop a hung FTP process from hanging the script. Say, count to 100 loops max or something. Its your choice how long to wait.

Home Next Preface Introduction Basic Shells Shell Syntax Built-In Commands Command Substitution Startup & Environment Pipes, Lists & Redirection Input & Output Using Files Design Considerations Functions Debugging Putting It All Together Appendix Code Examples
Page 210
This page was brought to you by rhreepe@injunea.demon.co.uk