Is there a linux command like mv but with regex?

For example I want to mv (.*?).sql $1.php,

is there a command that lets me specify renaming patterns?


Solution 1:

As others have mentioned, rename is good at this, but read the man page (man rename) before you try it. There are at least two entirely different tools out there called rename and which one you have will depend on your distribution. Calling them incorrectly can be dangerous.

Here's the man page for the perl-based version by Larry Wall that ships with Ubuntu. You give it a perl expression like rename 's/\.sql$/.php/' *.sql

Here's the man page for the rename that ships with older Red Hat and CentOS distributions. Usage is simple string substitution like rename .sql .php *.sql

You could also use a bash one-liner to process each file one at a time:

$ for f in *.sql; do mv -i "$f" "${f%%.*}.php"; done

Solution 2:

There's rename(1), which doesn't use regexes, but can solve your problem:

rename .sql .php *.sql

There's also mmv(1), but I'm unfamiliar with how it works.

Solution 3:

You could also try entering

for i in $(\ls -d *.sql)
do
mv $i $(echo $i | sed -e 's/\.sql$/\.php/')
done

Or to make it use regex's change it slightly to

for i in $(\ls -d | egrep -e '.*\.sql')
do
mv $i $(echo $i | sed -e 's/\.sql$/\.php/')
done

for a bit of shell coding fun.

Solution 4:

Being a plumber, I like pipes :)

Note: this answer is verbose - as a newbee I appreciate it when someone who knows takes the time to explain, so I am paying 'forward'. If you are not a newbee, please excuse the length and verbosity.

ls -1 *_201[67][0-9]* | sed -e 's/\(\(.\+\)[-_]\(201[67][0-9]\{4\}\)\([^.]\+\)\?\.[0-9a-z]\{2,3\}\)/mkdir -p \2\/\3; mv \1 \2\/\3\/\1/' | bash

Where:

ls -1 *_201[67][0-9]*

lists all files matching the pattern, in this case I am looking for files with the date in the filename in the form 'YYYYMMDD'

sed -e 's/\(\(.\+\)[-_]\(201[67][0-9]\{4\}\)\([^.]\+\)\?\.[0-9a-z]\{2,3\}\)/mkdir -p \2\/\3; mv \1 \2\/\3\/\1/'
#        : | |    |     |                  | |       |                    |:                                 :
#        : |^2....^    ^3..................^^4.......^                    |:                                 :
#        :^1..............................................................^:                                 :
#        /................filename.................pattern................./...mkdir...and....mv...command.../

^1: 1st capture group - the entire filename

^2: 2nd capture group - the 1st part of the filename pattern

^3: 3rd capture group - the 2nd filename part - here, the date as 'YYYYMMDD'

^4: 4th capture group - optional filename part between the date and the extension, and the extension itself

Sooooo....if I have a file named

CallLog_555123412_20161231-214403.7z

...then

mkdir -p \2\/\3; mv \1 \2\/\3\/\1

actually means

# make directory and sub-directory
mkdir -p CallLog_555123412/20161231
# move file from current directory to sub-sub-directory just created
mv CallLog_555123412_20161231-214403.7z CallLog_555123412/20161231/CallLog_555123412_20161231-214403.7z

Note that the output of the sed command is piped through to 'bash' to execute it. In order to first see the proposed file moves WITHOUT actually moving them, first remove the '| bash' at the end, and try it like so:

ls -1 *_201[67][0-9]* | sed -e 's/\(\(.\+\)[-_]\(201[67][0-9]\{4\}\)\([^.]\+\)\?\.[0-9a-z]\{2,3\}\)/mkdir -p \2\/\3; mv \1 \2\/\3\/\1/'

This will not make any changes at all, but you will see the command that will be executed IF you run it with '| bash' tagged on the end.

This is useful because you can create any sub-directories you want from parts in the filename, or anything else you add to the 'replacement' text in the sed command. Note that sed requires a lot of escape characters, though.

SED Regex brackets:

(...) require escaping for capture grouping --> \(...\)

{m,n} require escaping for quantifying previous object --> \{m,n\}

/ requires escaping in the replacement string --> \/

[...] DO NOT require escaping for character classes

\1 in the replacement string refers to the 1st captured group, and so on...

SECURITY WARNING: NEVER PIPE ANYTHING TO 'bash' UNLESS YOU FULLY UNDERSTAND THE OUTCOME...

Hope that helps clear some blockages :)

Solution 5:

Install mmv, then do this:

mmv "*.sql" "#1.php"