Unfortunately the find
command's -name
predicate only accepts a single pattern. If you want to search for multiple files by name you'd need to chain them with the -o
(logical OR
) operator - something like:
find Documents/ \( -name "file2.txt" -o -name "file5.txt" -o -name "file9.txt" \) -print
This makes it tricky to construct the search programmatically from a list; the closest I can come to your attempted command is:
read the list into a shell array
mapfile -t files < list.txt
use the bash shell's printf
to construct the predicate list
printf -- '-name "%s" -o ' "${files[@]}"
use eval
to evaluate the resulting command string
There's a wrinkle here inasmuch as if we use printf
's format re-use feature to construct the list in this way, we're left with a 'dangling' -o
; we can work around this by terminating the list with a -false
test (since -o -false
is a Boolean no-op) so that our final predicate string becomes
"\( $(printf -- '-name "%s" -o ' "${files[@]}") -false \)"
Putting it all together - given
$ tree dir
dir
├── Folder_1
│ ├── file1.txt
│ ├── file2.txt
│ ├── file3.txt
│ └── file4.txt
├── Folder_2
│ ├── file5.txt
│ ├── file6.txt
│ └── file7.txt
└── Folder_3
├── file8.txt
└── file9.txt
3 directories, 9 files
and
$ cat list.txt
file2.txt
file5.txt
file9.txt
then
$ mapfile -t files < list.txt
$ eval find dir/ "\( $(printf -- '-name "%s" -o ' "${files[@]}") -false \)" -print
dir/Folder_1/file2.txt
dir/Folder_2/file5.txt
dir/Folder_3/file9.txt
To copy files instead of just listing them, you could then do
$ mkdir newdir
$ eval find dir/ "\( $(printf -- '-name "%s" -o ' "${files[@]}") -false \)" -exec cp -t newdir/ -- {} +
resulting in
$ tree newdir
newdir
├── file2.txt
├── file5.txt
└── file9.txt
0 directories, 3 files
Note: the eval
command is powerful and potentially open to abuse: use with care.
In practice, given that you appear to want to find only a small number of files, the KISS approach would be to accept the performance hit of multiple find
calls and just use a loop:
while read -r f; do
find dir/ -name "$f" -exec cp -v -- {} newdir/ \;
done < list.txt
or even using xargs
xargs -a list.txt -n1 -I@ find dir/ -name @ -exec cp -v -- {} newdir/ \;
I would just use grep
and sort -u
:
$ grep -Po '/elv/\K[^/]+' NASA_access_log_Aug95.txt | sort -u
ATLAS_CENTAUR
DELTA
TITAN
The -P
enables Perl Compatible Regular Expressions which let us use \K
which means "ignore anything matched up to this point". The -o
means "only show the matching portion of the line". Then, the regular expression means "look for /elv/
, ignore everything matched until the /elv/
, and then look for one or more non-/
characters ([^/]+
).
Best Answer
In
sed
, assuming your list is in a file calledfile
, you could use:-n
don't print anything until we ask for it/ss: /
find the patternss:
(to match the lines withIP address:
)N
read the next line too so we can join them;
separates commands, like in the shells/old/new/
replaceold
withnew
s/\n//
delete the newline between the two linesp
print the lines we've worked onWhen you see what you want, repeat the command adding
> newfile
at the end of it to write the modified file tonewfile
More readably:
(
tee
helpfully writes to thenewfile
and displays the output at the same time)