0

I am trying to change my code to read a .txt file from a directory. That directory contains only one .txt file but I do not know its name beforehand. I only know it is a .txt file. Is it possible to do that using shell script?

Below is my current code to read a file but I have to manually specify the file name.

#!/bin/bash

declare -a var

filename='file.txt'
let count=0

while read line; do
    var[$count]=$line
    ((count++))
done < $filename
Foody
  • 177
  • 1
  • 10
  • Is there a good guide page on how to find existing articles if you don't know what keys to search for? The term `glob` is pretty central to this question, but OP apparently didn't know to use it. What's our best way to help? Using our experience to explain and post links in the comments? – Paul Hodges Aug 20 '21 at 13:59

3 Answers3

2

If you are 100% sure that there is only one matching file just replace:

done < $filename

by:

done < *.txt

Of course this will fail if you have zero or more than one matching file. So, it would be better to test first. For instance with:

tmp=$(shopt -p nullglob || true)
shopt -s nullglob
declare -a filename=(*.txt)
if (( ${#filename[@]} != 1 )); then
  printf 'error: zero or more than one *.txt file\n'
else
  declare -a var
  let count=0
  while read line; do
    var[$count]=$line
    ((count++))
  done < "${filename[0]}"
fi
eval "$tmp"

The shopt stuff stores the current status of the nullglob option in variable tmp, enables the option, and restores the initial status at the end. Enabling nullglob is needed here if there is a risk that you have zero *.txt file. Without nullglob that would store literal string *.txt in array filename.

Your loop could be optimized a bit:

  declare -a var
  while IFS= read -r line; do
    var+=("$line")
  done < "${filename[0]}"

IFS= is needed to preserve leading and trailing spaces in the read lines. Remove it if this is not what you want. The -r option of read preserves backslashes in the read line. Remove it if this is not what you want. The += assignment automatically adds an element at the end of an indexed array. No need to count. If you want to know how many elements you array contains just use ${#var[@]} as we did to test the length of the filename array.

Note that storing the content of a text file in a bash array is better done with mapfile if your bash version is recent enough. See this other question, for instance.

Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
  • I'm wondering if it were not better to store the file name in a normal var and then check if it's a readable file. `filename=*.txt; if [[ -r "$filename" ]]; then...` Or similar. – user1984 Aug 20 '21 at 12:58
  • 1
    `filename=*.txt` assigns the string `*.txt` to variable `filename`. From `bash` manual: "_A variable may be assigned to by a statement of the form `name=[value]`...Pathname expansion is not performed._" – Renaud Pacalet Aug 20 '21 at 13:23
  • True. Maybe `filename=$(echo *.txt)` instead. The checking for the file part was what I thought is important. `[[ -r $filename ]]`, or any of the file checking variants that is appropriate. – user1984 Aug 20 '21 at 13:31
1

Parameter expansion can be triggered in assignments as well!

filename=$(echo -n *.txt)

If you want to make it foolproof (catching the case that the number of matching files is not 1), assign to an array and check its size:

farr=( *.txt )
if (( ${#farr[*]} != 1 ))
then
  echo You lied to may when you said that there is exactly on .txt file
else
  filename=${farr[0]}
fi
user1934428
  • 19,864
  • 7
  • 42
  • 87
  • 1
    _Parameter expansion works in assignments as well!_: does not work here. `touch foo.txt; f=*.txt; echo "$f"` prints `*.txt`. But of course, if you `echo $f` (no quotes) the value of variable `f` undergoes pathname expansion, producing the expected `foo.txt`. – Renaud Pacalet Aug 20 '21 at 13:22
  • Good point! I will update my anser accordingly. – user1934428 Aug 22 '21 at 13:36
-1

You can use this. The head command is used in this context to ensure one result.

filename=$(ls *.txt | head -n 1)
NathanL
  • 357
  • 2
  • 8
  • 1
    If you are not sure that there will be one file only you should probably also consider that there could be none. Moreover `ls` is not really intended for this. If the filename contains a newline, for instance, your command will not behave as you expect. You can test with: `touch $'foo\nfoo.txt'; ls *.txt | head -n 1`. Other special characters could cause other issues. – Renaud Pacalet Aug 20 '21 at 12:49