Redirection via exec

An exec <filename command redirects stdin to a file. From that point on, all stdin comes from that file, rather than its normal source (usually keyboard input). This provides a method of reading a file line by line and possibly parsing each line of input using sed and/or awk.

#!/bin/bash # Redirecting stdin using 'exec'.

exec 6<&0 # Link file descriptor #6 with stdin. # Saves stdin.

exec < data-file # stdin replaced by file "data-file"

read a1 # Reads first line of file "data-file". read a2 # Reads second line of file "data-file."

echo echo "Following lines read from file." echo "-------------------------------" echo $a1 echo $a2

echo; echo; echo

exec 0<&6 6<&- # Now restore stdin from fd #6, where it had been saved, #+ and close fd #6 ( 6<&- ) to free it for other processes to use. # # <&6 6<&- also works.

echo -n "Enter data " read b1 # Now "read" functions as expected, reading from normal stdin. echo "Input read from stdin." echo "----------------------" echo "b1 = $b1"

echo

exit 0

Similarly, an exec >filename command redirects stdout to a designated file. This sends all command output that would normally go to stdout to that file.

Important: exec N > filename affects the entire script or current shell. Redirection in the PID of the script or shell from that point on has changed. However . . .

N > filename affects only the newly-forked process, not the entire script or shell.

#!/bin/bash # reassign-stdout.sh

LOGFILE=logfile.txt

exec 6>&1 # Link file descriptor #6 with stdout. # Saves stdout.

exec > $LOGFILE # stdout replaced with file "logfile.txt".

# ----------------------------------------------------------- # # All output from commands in this block sent to file $LOGFILE.

echo -n "Logfile: " date echo "-------------------------------------" echo

echo "Output of \"ls -al\" command" echo ls -al echo; echo echo "Output of \"df\" command" echo df

# ----------------------------------------------------------- #

exec 1>&6 6>&- # Restore stdout and close file descriptor #6.

echo echo "== stdout now restored to default == " echo ls -al echo

exit 0

Redirecting both stdin and stdout in the same script with exec

#!/bin/bash # upperconv.sh # Converts a specified input file to uppercase.

E_FILE_ACCESS=70 E_WRONG_ARGS=71

if [ ! -r "$1" ] # Is specified input file readable? then echo "Can't read from input file!" echo "Usage: $0 input-file output-file" exit $E_FILE_ACCESS fi # Will exit with same error #+ even if input file ($1) not specified (why?).

if [ -z "$2" ] then echo "Need to specify output file." echo "Usage: $0 input-file output-file" exit $E_WRONG_ARGS fi

exec 4<&0 exec < $1 # Will read from input file.

exec 7>&1 exec > $2 # Will write to output file. # Assumes output file writable (add check?).

# ----------------------------------------------- cat - | tr a-z A-Z # Uppercase conversion. # ^^^^^ # Reads from stdin. # ^^^^^^^^^^ # Writes to stdout. # However, both stdin and stdout were redirected. # Note that the 'cat' can be omitted. # -----------------------------------------------

exec 1>&7 7>&- # Restore stout. exec 0<&4 4<&- # Restore stdin.

# After restoration, the following line prints to stdout as expected. echo "File \"$1\" written to \"$2\" as uppercase conversion."

exit 0

Avoiding a subshell

I/O redirection is a clever way of avoiding the dreaded inaccessible variables within a subshell problem.

#!/bin/bash # avoid-subshell.sh # Suggested by Matthew Walker.

Lines=0

echo

cat myfile.txt | while read line; do { echo $line (( Lines++ )); # Incremented values of this variable #+ inaccessible outside loop. # Subshell problem. } done

echo "Number of lines read = $Lines" # 0 # Wrong!

echo "------------------------"

exec 3<> myfile.txt while read line <&3 do { echo "$line" (( Lines++ )); # Incremented values of this variable #+ accessible outside loop. # No subshell, no problem. } done exec 3>&-

echo "Number of lines read = $Lines" # 8

echo

exit 0

# Lines below not seen by script.

$ cat myfile.txt

Line 1. Line 2. Line 3. Line 4. Line 5. Line 6. Line 7. Line 8.