I am attempting to recursively search through a list of files and if the file contains a string, rename the file to the grep results of said string.

The sample files contain a the following content:

foo bar1

foo bar2

foo bar3

foo bar4

foo bar5

grep + awk return the results that I need:

$ grep -r "^foo" . | awk '{print $2}'

I'm stuck at passing these results to a mv command.

$ grep -r "^foo" . | awk '{print $2}' | xargs -I{} mv {} .
mv: cannot stat 'bar1': No such file or directory
mv: cannot stat 'bar2': No such file or directory
mv: cannot stat 'bar3': No such file or directory
mv: cannot stat 'bar4': No such file or directory
mv: cannot stat 'bar5': No such file or directory

Thanks in advance. Gnu/BSD Grep both have the same results.


3 Answers 3


I would use a shell for-loop:

for match in "$(grep -ro '^foo.*')";do
    echo mv "${match%:*}" "${match#*:}"

This will iterate all the matching file:matching-substring and use the % and # string operators to delete everything up to, resp. everything after, and including :.

Note that if you really want to match the whole line as opposed to just the substring that matches the pattern, use

for match in $(grep -r '^foo');do

Make sure to double-quote since matches and/or filenames could contain spaces.

If you want to match by one pattern but rename the file to the second word in the matching line:

for match in "$(grep -ro '^foo.*')";do
    fname=$("echo ${match#*:}|awk '{print $2}'")
    echo mv "${match%:*}" "$fname"
  • If I'm going to pipe the results of grep to awk to return the second record, would i modify it like so: for match in "$(grep -ro '^foo' . | awk '{print $2}')";do echo mv "${match%:*}" "${match#*:}"; done
    – Bert
    Nov 2, 2016 at 19:01
  • What do you mean with "the second record"? If you mean the second match (i.e. the second line where the grep pattern matched), you don't need a loop, just pipe the grep call to sed -n '2p'.
    – kba
    Nov 2, 2016 at 22:38
  • If you mean to rename by the second word, see the last variant in my answer.
    – kba
    Nov 2, 2016 at 22:45

You can do what you are trying to accomplish using perl:

#!/usr/bin/env perl
use strict;

my $dir = "/path/to/directory";
chdir $dir or die "Cannot open $dir!";

my @files = grep { -f } glob("*");

foreach my $file(@files) {
  open F1, "<", $file or die "Cannot open file $file! $!\n";
  while ( <F1> ){
    my @strings = split(' ');
    if($strings[1] =~ /bar/){
      system("/bin/mv $file $strings[1]");
  • Will this be recursive?
    – kba
    Nov 2, 2016 at 22:45
  • No, it will only look for files in $dir. You could extend the program to recurse, but if you have a lot of files in subdirectories you could use [perldoc.perl.org/File/Find.html] to populate @files
    – neofug
    Nov 2, 2016 at 23:20

find . -type f -exec egrep -il '^foo' {} \; | sort | while IFS='' read -r line; do mv -n "$line" "$(dirname "$line")"'/'"$(egrep -i '^foo' "$line" | awk '{ print $2 }')"; done

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.