I have around 8 sub directories that have image files in them. I need to move 300 of around 6000 photos from those sub directories to one common directory. I have a file with the names, not the paths, of the files I need moved. How can I do this?
I have seen things close but nothing where I can read a name in a file, search for that name in the sub directories to get the path and then move that file to the common directory then move on to the next. I can’t find a way to make find read in a file. This will be on a MacOS computer but anything with Python or bash should work just fine. I’m horrible with coding and especially with multiple variables. I thought I could cat the file then somehow send it to find which then can send that directory path to the copy command but the find command is the killer for me.
Thanks for any help that works.
In this case
find is not the best tool. With some effort one could craft a command that reads from a file and builds a
find command that checks several directories for several names; or multiple
find commands: one per name or even one per a member of Cartesian product of names and directories.
Still tests like
-regex (if supported) take patterns. I understand you want to supply exact names. Probably the names are safe, but maybe not. E.g. a name
Awesome.Picture.*OMG*.jpg can match more than this exact string if interpreted as a filename pattern or a regular expression. In general you need to sanitize each name, so after being interpreted it matches the original string only.
Working with exact names is easy in a shell. In case of 300 names and about 8 directories to check, their Cartesian product contains about 2400 pairs. Even a slow shell can check them in reasonable time. This will probably be negligible in comparison to how much time the actual copying will take.
These are my assumptions:
- The file with names specifies one filename per line.
- Each line in the file is terminated by a newline character that does not belong to the respective filename. Note this assumption applies to the last line as well (compare this answer).
- Each line specified in the file should be taken literally. Spaces (if any) belong to the filename; the same for quotes, backslashes etc.
- You want to check few exact directories, not their subdirectories (i.e. no recursive search).
This should do:
"$1//." shift while IFS= read -r name; do for dir do source="$dir/$name" test -f "$source" && </dev/tty cp -i -- "$source" "$target" && break done donetarget=
Save it as
contraption and make it executable (
chmod +x contraption). Use it like this:
</file/with/names ./contraption /target/dir /source/dir1 /source/dir2 /another/source
Specify more source directories if you want.
- It was my intention to write portable code. I think the code is portable, although in general one may need to adjust the shebang because POSIX does not force
shto be in
/bin/sh; nor does it force
envto be in
#!/usr/bin/env shin theory is not better; in practice see this answer).
IFS= read -r nameis explained here: Understanding
IFS= read -r line.
- Because of
test -fthe code will not (try to) copy matching files that are not regular files.
</dev/tty cp -iis to protect files already existing in the destination directory. Standard
cp -iwould only break things (because it would read from stdin which is the file). Note
/dev/ttyis specified by POSIX.
--is explained here: What does
sh -is explained here: Why the
#! /bin/sh -shebang?
- The last slash in
target="$1//.") ensures it’s a directory. The dot is here only because I wanted my code to be overly safe (see this answer, it explains both). If you specify the first positional parameter with a trailing slash already, it should not matter. I inject two slashes (not one) to get
//.) in case you specify
/. The point is
//.may be troublesome. Note you will get
//.if you specify an empty string as the target (the solution is not to specify an empty string, it’s not a valid pathname).
breakcauses the script to move on to the next name if copying succeeds, without checking the current name in not-yet-checked directories. If you expect the directories to contain files with identical names, in the command line specify preferred source directories before less preferred source directories.