0

How to display all existing regular file names from a text file?

Example:

$ cat file.txt
zzz
xxx/yyy
234 546
"abc def"
--bbb

$ cat file.txt | <one-liner>
zzz
xxx/yyy

In this list from file.txt regular files zzz and xxx/yyy exist (for example). Hence, the <one-liner> displays zzz and xxx/yyy.

What would be an implementation of this, preferably as a one-liner?

I've quickly tried to play with something like xargs -I {} --max-lines=1 test -f "{}". However, it seems that it's not the right path.

8
  • @ChrisDavies I want to verify if each line in the file corresponds to a file in the filesystem. I've updated the question (use of "display" [taken from man echo] rather than "print").
    – pmor
    Commented May 21 at 11:05
  • 1
    Edit the question and add the expected output. Commented May 21 at 11:05
  • Are you expecting "abc def" to be treated literally (the filename has those quote characters), or do you expect that to be treated as abc def (the quotes only protect the whitespace)? What happened when you tried your xargs commands? Do you have example lines in which it failed to work as expected?
    – muru
    Commented May 21 at 11:52
  • @muru The "abc def" is expected to be treated as file named abc def.
    – pmor
    Commented May 21 at 12:11
  • 1
    @ChrisDavies Indeed, to account quote marks, it seems that the "abc def" is expected to be treated as file named "abc def". In my case there are no files with whitespaces nor with quote marks.
    – pmor
    Commented May 22 at 13:11

3 Answers 3

4

The starting point for this could be a simple loop:

while IFS= read -r item
do
    [ -f "$item" ] && printf '%s\n' "$item"
done <file.txt

This could be crashed onto a single line at the expense of readability:

while IFS= read -r item; do [ -f "$item" ] && printf '%s\n' "$item"; done <file.txt

Or you could use Perl:

perl -lne 'print if -f' file.txt
7
  • Why -f and not -e? Do we want to exclude directories, fifos etc? And you could simplify your perl to perl -lne 'print if -e' file.txt.
    – terdon
    Commented May 21 at 11:48
  • I chose -f specifically because of the question, which states "files". Couldn't get that perl construct to return any files; I assumed it was the trailing NL that stopped -f matching Commented May 21 at 12:43
  • Hmm fair. I assumed they wanted everything since that distinction (between "regular" files and weirdness) isn't very commonly known. You could well be right though. As for the NL yes, exactly, but -l turns on auto chomp.
    – terdon
    Commented May 21 at 12:51
  • Ah, thanks. I have to hold my hand up and say I'd forgotten -l completely (I don't use perl much these days). Simplifying... Commented May 21 at 13:09
  • Could be perl -lne 'print if lstat' for files of any type. -e would exclude symlinks that can't be resolved. Commented May 22 at 15:07
3

Here's one way:

while IFS= read -r file; do
  [ -f "$file" ] && printf '%s\n' "$file"
done < file.txt

And sure, you can stick it on one line, if you prefer:

$ while IFS= read -r file; do [ -f "$file" ] && printf '%s\n' "$file"; done < file.txt
zzz
xxx/yyy

You can use the cat file.txt | command approach, if you want also. Something like:

$ cat file.txt | xargs -I {} sh -c '[ -f "$1" ] && printf "%s\n" "$1"' sh {}
zzz
xxx/yyy
6
  • 1
    The last one should perhaps be cat file.txt | xargs -I {} sh -c '[ -e "$1" ] && printf "%s\n" "$1"' sh {} to avoid possible code injection Commented May 21 at 20:11
  • Changed, @steeldriver, thanks. Just to be sure I get it, the injection danger came from the unquoted {} at the end of cat file.txt | xargs -I {} sh -c '[ -e "{}" ] && printf "%s\n" {}' since, if file.txt can contain executable commands, something like $(rm *) say, that would have been executed, correct?
    – terdon
    Commented May 22 at 9:02
  • Yes that's it - even just a line like foo ; rm somefile (you can see what happens if you run sh -xc in place of sh -c) Commented May 22 at 11:47
  • You can't use xargs without -0/-d '\n' GNU extensions here or it will break on file names starting with whitespace or containing quotes or backslashes. Commented May 22 at 14:52
  • Sorry, @StéphaneChazelas, I basically never use xargs, I'm too used to writing shell loops, so I don't know exactly what you mean. Could you make the edit?
    – terdon
    Commented May 22 at 14:58
1

With zsh:

print -rC1 -- ${(f)^!$(<file.txt)"}(N)

Would print those of those files that can be found regardless of their type.

print -rC1 -- ${(f)^!$(<file.txt)"}(N.)

To limit to regular files. Or:

print -rC1 -- ${(f)^!$(<file.txt)"}(N-.)

To also include symlinks eventually resolving to regular files (like [ -f does).

Replace . with ^/ for file of any type except directory (regular files, symlinks, fifos, sockets...).

With other shells, for file of any type, with GNU xargs and ls:

xargs -rd '\n' -a file.txt ls -1dU -- 2> /dev/null

With GNU find 4.9 or newer:

<file.txt tr '\n' '\0' | find -files0-from - -prune -xtype f 2> /dev/null

For files that can be found to be of type regular after symlink resolution.

You must log in to answer this question.

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