mora
mora

Reputation: 2287

Efficiently rereading (or otherwise reusing) content from an input file in bash

I would like to apply a commands to files in a directory, target_dir, by the following code.

for t_file in $(find 'target_dir' -maxdepth 1 -type f);
do
  exec {fd}<'command_list.txt'
  while read -u "${fd}" eval_command
  do
    eval "${eval_command}"
  done
  exec {fd}>&-
done

A example of command_list.txt is

# command_list.txt
cat "${t_file}"

The program loads the command_file.txt for every files but I expects that it is more efficient if I can move the file pointer back to the first line of the file without needing to close and reopen it between iterations.

exec {fd}<'command_list.txt'

for t_file in $(find 'target_dir' -maxdepth 1 -type f);
do

  (move cursor of read to the first line of 'command_list.txt')

  while read -u "${fd}" eval_command
  do
    eval "${eval_command}"
  done

done

exec {fd}>&-

Is seeking a file pointer back to the beginning of a file without reopening it possible in bash?

Upvotes: 0

Views: 151

Answers (2)

Charles Duffy
Charles Duffy

Reputation: 295472

To answer the literal question: You can seek a file descriptor in bash only with a loadable module (plugin) to the shell adding a new builtin, or by spinning up an external program (inheriting your file descriptors) and asking it to do the seek operation (an approach given in this answer). However, the cost of spinning up an external program is larger than the cost of just closing and reopening your file, so that's not approach that really makes sense in this case.


If you want to store your command list, just do that -- store it as an array. If by "moving the cursor" you're referring to seeking within the input file, bash doesn't provide a seek primitive in the default set of builtins -- but there's no particular need for one anyhow.

# read command_list.txt only once, storing its contents in an array
readarray -t eval_commands < command_list.txt

while IFS= read -r -d '' t_file <&3; do          # iterate over filenames from find
  for eval_command in "${eval_commands[@]}"; do  # iterate over your array
    eval "$eval_command"
  done
done 3< <(find target_dir -maxdepth 1 -type f -print0)

By the way -- if you were going to pass your filename as an argument to the command rather than substituting it in, you'd want to do so as follows:

# put the filename expansion in *single quotes*
eval "$eval_command "'"$t_file"'

...or as follows:

# generate a safe version of the filename
printf -v t_file_q '%q' "$t_file"

# ...and use that safe version instead of the original
eval "$eval_command $t_file_q"

If one ran eval "$eval_command $t_file" -- not following either of these precautions -- a file in created with touch $'target_dir/hello world $(rm -rf $HOME)' would be very bad news.

Upvotes: 2

Stephan
Stephan

Reputation: 581

find 'target_dir' -maxdepth 1 -type f -exec cat {} \;

That would cat every file in the target directory 1 folder deep.

Upvotes: 0

Related Questions