11

I have some files in a directory as follows.

source_dir: 
ABCD.HRA.0014.2.200.png
ABCD.HRA.0015.2.200.png
ABCD.HRA.0016.2.200.png
MMNP.HRA.0016.2.200.png

I also have a text file with following content.

text.txt:

ABCD.HRA.0014
ABCD.HRA.0015

Now is there any way I can transfer the files as per the string mentioned in text.txt. After command, source dir and dest_dir should be as follows.

source_dir: 
ABCD.HRA.0016.2.200.png
MMNP.HRA.0016.2.200.png

dest_dir: 

ABCD.HRA.0014.2.200.png
ABCD.HRA.0015.2.200.png
  • how many items is there in the source_dir? it looks big – bac0n Nov 11 at 12:02
  • 1
    Wouldn't that be moving, not copying? – Solomon Ucko Nov 12 at 1:13
11

grep -f allows you to use text.txt as a source for patterns.

#!/bin/bash  
for i in source_dir/*.png; do  
  if grep -Fq -f text.txt <<< "$i"; then  
    mv -t dest_dir "$i"  
  fi  
done  
$ ls
dest_dir  script.sh  source_dir  text.txt

grep options:

  • -F Interpret patterns as fixed strings, not regular expressions.
  • -q Do not write anything to stdout.
  • -f Obtain patterns from FILE, one per line.

Here strings:

  • <<< A variant of here documents, does variable expansion before sending the string.

mv options:

  • -t Move all source arguments into -t directory.
  • here xx is a random don't care string. But it seems like in your answer xx on both sides is fixed. – Abdul Karim Khan Nov 11 at 9:47
  • are there underscores in those parts? – bac0n Nov 11 at 10:00
  • no. It should be like 'dontcaretext{stringtofind}dontcaretext'. the dontcaretext can be anything and of any length and is not same in all files. – Abdul Karim Khan Nov 11 at 10:07
  • I have edited my question and provided the real example. – Abdul Karim Khan Nov 11 at 10:23
  • Bu using the above comment, it moves all the files instead of just those mentioned in text.txt. – Abdul Karim Khan Nov 11 at 10:27
10

Your files all seem to end in .2.200.png, hence we can use the input file only:

while read line ; do
  mv "source_dir/${line}.2.200.png" destination_dir/
done < text.txt
  • wow very simple, good! – Lety Nov 11 at 11:09
  • Even simpler: taking the OP to the letter, mv "source_dir/*${line}*" destination_dir/ (+1) – gboffi Nov 12 at 14:53
3

One way, if you know all the filenames in the file contain no whitespace, is like this:

cp $(cat text.txt) targetdir/

I always add the / at the end - this makes the command fail if targetdir doesn't exist and is a directory; you want that to happen, because otherwise you may end up copying all the files into one called targetdir (actually, I think that doesn't happen anymore in modern bash; I'm that old)

If each line in the file contains one filename, which may contain whitespace, the this method works, at least in ksh and newer versions of bash:

cat text.txt | while read l
do
cp "$l" targetdir/
done

And if that doesn't work in your version of bash, then this does (note the < text.txt after done):

while read l
do
cp "$l" targetdir/
done < text.txt
  • +1 for correcting my goof. To clarify a detail, though, if target did doesn’t exist and the slash is omitted, each file overwrites the previous, leaving only the last file copied. – WGroleau Nov 12 at 16:02
3

Update: j4nd3r53n is correct: this won’t work for any file names that contain whitespace. Apologies for my “senior moment”—what I intended (and have often done) was to pipe it to while as I have shown after. I will not delete the wrong one as it may provide a useful warning to those that might try it.

One more flawed method (see warning above):

for STR in $(cat file); do # flawed
  cp "source/*${STR}*" dest
done

Here is the method I originally meant:

cat file | while read STR # better
  cp "source/*${STR}*" dest
done
  • The problem with this method is, if the filenames do have whitespace embedded, it won't work. – j4nd3r53n Nov 12 at 8:58
  • Not so. I have done it often. The quote marks cause everything between them to be treated as a unit. – WGroleau Nov 12 at 15:31
  • $(cat file) will return a string of words separated by spaces, which STR will pick up as individual tokens. The quotes are not in effect at this point. – j4nd3r53n Nov 12 at 15:35
  • @j4nd3r53n, I’d be tempted to believe you if I hadn’t seen it work hundreds of times in my forty years of software engineering and home tinkering. – WGroleau Nov 12 at 15:46
  • For anyone reading these comments, @j4nd3r53n is correct—I have edited a disclaimer into my answer. – WGroleau Nov 12 at 15:54
3

Another way using find:

find source_dir -type f | while read file; do  name=$(basename $file); grep ${name%.2.200.png} text.txt && mv -v $file dest_dir; done 

Find file in source_dir and loops over the list.

For each file runs grep in text.txt. It is necessary to get the file name using basename command to exclude source_dir folder.

Finally if grep returns true, moves file in dest_dir.

Adding -v to mv command and removing -q to grep command, you can see the action.


Here is an examples of How to use it:

$ ls source_dir
ABCD.HRA.0014.2.200.png  MMNP.HRA.0016.2.200.png  shrr.2.200.png
ABCD.HRA.0015.2.200.png  sghd.2.200.png
ABCD.HRA.0016.2.200.png  shdj.2.200.png

$ cat text.txt
ABCD.HRA.0014
ABCD.HRA.0015

$ mkdir dest_dir

$ find source_dir -type f | while read file; do name=$(basename $file); grep ${name%.2.200.png} text.txt && mv -v $file dest_dir; done
ABCD.HRA.0014
`source_dir/ABCD.HRA.0014.2.200.png' -> `dest_dir/ABCD.HRA.0014.2.200.png'
ABCD.HRA.0015
`source_dir/ABCD.HRA.0015.2.200.png' -> `dest_dir/ABCD.HRA.0015.2.200.png'
  • Can you confirm it? I used it and it copies no file in destination directory – Abdul Karim Khan Nov 11 at 10:15
  • I added options that view the action. On my Linux Redhat system it works, but for now I don't have the possibility to test it on ubuntu – Lety Nov 11 at 10:51
  • Updated with new file pattern. – Lety Nov 11 at 11:05
  • 1
    If source has subdirectories containing some of the files, you need find but if not, other methods will be faster. – WGroleau Nov 11 at 16:56
  • This works but moves only the first file in text.txt. – Abdul Karim Khan Nov 11 at 23:44
2

A bit convoluted, but you could use awk (which in this case does nothing more than reading the file out line by line)

awk '{system("mv " $0 "* dest_dir/")}' text.txt 

which basically says 'for each line, do mv BASE_STR* dest/'

But the approach is very dependent on your specific use case, this might not work if you want to make it more generic.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.