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

You must log in to answer this question.

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