14

How do you get the first file in a directory in bash? First being the what the shell glob finds first.

My search for getting the first file in a directory in bash brought me to an old post with a very specific request. I'd like to document my solution to the general question for posterity and make a place for people to put alternative solutions they'd like to share.

3
  • By "file", do you mean "regular file", or are you also interested in directories, symbolic links and other types of files?
    – Kusalananda
    Commented Sep 25, 2020 at 6:26
  • Good point. I was only considering regular files.
    – mcp
    Commented Nov 2, 2020 at 1:52
  • 2
    What do you mean by "the first" file? First to be added to the directory, or first chronologically or as sorted using $LC_COLLATE, or something else? Commented Dec 6, 2021 at 8:38

6 Answers 6

29

To get the first file in the current dir you can put the expansion in an array and grab the first element:

files=(*)
echo "${files[0]}"
# OR
echo "$files" # since we are only concerned with the first element

Assuming your current dir contains multiple dirs you can loop through and grab the first file like so:

for dir in *; do
    files=($dir/*)    
    echo "${files[0]}"
done

But be aware that, depending on your shell settings, files=(*) may return an array of one element (that is '*') if there are no files. So you have to check that the file names in the array correspond to files which actually exist (and bear in mind that * is a valid file name).

2
  • This gives you the first name from the lexicographically sorted list of non-hidden names in the current directory. This may not be a regular file. In comments to the question, you specifically asked about regular files, though.
    – Kusalananda
    Commented May 21, 2022 at 7:51
  • any idea why such a simple scenario is so complicated. I asked chatGPT and gave me mv "$(ls | head -n 1)" /path/to/target/directory/ which lists 2 files Commented Jul 31, 2023 at 23:02
3

All existing answers seem to first create a full listing of the directory, which can take a while with thousands of elements, so I ended up with the simple:

ls -AU | head -1

Where -U means unsorted and thus produces an immediate result.

2
  • This also returns names that may not be regular files. Note that -U is a non-standard GNU extension. Using find . ! -name . may be better, but you still would only get the first part of the name if it contains newlines since head acts on text, not filenames.
    – Kusalananda
    Commented May 21, 2022 at 7:58
  • You can use -f, seems to do the same and is standard. Filetypes are not a requirement here, but one could filter using file and test if needed. As for newlines, one can use --zero in ls, -z on head and -print0 on find (or use its exec flag).
    – xeruf
    Commented May 24, 2022 at 22:06
2

In zsh:

  • first non-hidden file in locale collation order: first=(*(N[1]))
  • same, but restricted to non-directory files: first=(*(N^/[1]))
  • same, but also excluding symlinks to directories: first=(*(N^-/[1]))
  • restricting to regular files: first=(*(N.[1]))
  • same but including symlinks to regular files: first=(*(N-.[1]))

Some more notes:

  • those define an array variable as it still needs to be able to store a variable number of elements: 0 (no matching file) or 1 (matching files, among which only the first is selected). To define a $first scalar variable instead (and have it contain the empty string if there's no matching file), you can do (){ first=$1; } *(N[1]) instead. Or to leave the $first scalar variable untouched instead if there's no matching file: (){ (($#)) && first=$1; } *(N[1]).
  • to include hidden files, add the D glob qualifier
  • in some locales (including most of the ones typically used on GNU systems from 2020 like en_US.UTF-8), collation order is not always deterministic as some characters sort the same. See for instance after touch 🧙 🧚 🧛 🧜 🧝 on Ubuntu 20.04. All those files will have the same sorting order, so which one you'll get first will be more or less random.
  • with zsh glob qualifiers, it's also possible to change the order: n makes the filename comparison numerical (so that file2 comes before file10 for instance), and with the o (O for reverse) glob qualifiers, one can sort based on other criteria than name (such as age, size or even arbitrary shell code...).
2

Almost is an this answer:

shopt -s nullglob
set -- *
printf "%s\n" "$1"

Or:

(( $# )) && printf "%s\n" "$1"

To skip printing anything if there's no non-hidden file in the current working directory.

1

To get the name of the regular file that sorts first in a directory, you may use

shopt -s nullglob dotglob
unset -v name

for name in some/path/*; do
    [ -f "$name" ] && break
    unset -v name
done

After this loop, $name would either be the name of the regular file that sorts first, or the variable would be unset if there are no regular files in the directory some/path.

The shell options used here make sure that the loop is not run if the pattern does not match (nullglob), and that we also match hidden names (dotglob).

You'll get the first name since filename globbing patterns are expanded into lexicographically sorted lists.

0

Solution using only 'find':

find  /tmp  -type d -exec find {}  -depth -maxdepth 1 -type f -print -quit \;

This will print the first filename, including path, for every directory in /tmp.

 find /tmp  -type d -exec find {} -depth -maxdepth 1 -type f ! -name '.*' -print -quit \; 

This will print the first filename in every directory, but skip over DOT files.

Works both on Linux and Mac.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .