55

I want to rename all the files in a folder which starts with 123_xxx.txt to xxx.txt.

For example, my directory has:

123_xxx.txt
123_yyy.txt
123_zzz.txt

I want to rename all files as:

xxx.txt
yyy.txt
zzz.txt

I have seen some useful bash scripts in this forum but I'm still confused how to use it for my requirement.

Let us suppose I use:

for file in `find -name '123_*.txt'` ; do mv $file {?.txt} ; done

Is this the correct way to do it?

oguz ismail
  • 1
  • 16
  • 47
  • 69
user1225606
  • 1,123
  • 2
  • 14
  • 14

15 Answers15

92

You can do it this way:

find . -name '123_*.txt' -type f -exec sh -c '
for f; do
    mv "$f" "${f%/*}/${f##*/123_}"
done' sh {} +

No pipes, no reads, no chance of breaking on malformed filenames, no non-standard tools or features.

sorpigal
  • 25,504
  • 8
  • 57
  • 75
  • 3
    This is making use of [parameter expansion](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_0602) to strip the basename from the end of the path, then append a new name which has had the prefix up to 123_ removed. The two operators to read about are `%` and `##`, the rest is simple globbing. – sorpigal Jun 10 '21 at 21:45
40
find . -name "123*.txt" -exec rename 's/^123_//' {} ";" 

will do it. No AWK, no for, no xargs needed, but rename, a very useful command from the Perl lib. It is not always included with Linux, but is easy to install from the repos.

Nick
  • 3,172
  • 3
  • 37
  • 49
user unknown
  • 35,537
  • 11
  • 75
  • 121
  • This is superior to the overkill `awk` and looping answers above for those people who have the perl `rename`. – sorpigal Feb 22 '12 at 12:48
  • 4
    you miss a dot (.) before -name, doesn't run on mac – Chan Le Sep 07 '14 at 12:58
  • @ChanLe Gnu find on Linux doesn't require a directory name to act upon, but takes '.' automatically, if a name is missing. – user unknown Sep 07 '14 at 19:58
  • 2
    `brew install rename` on Mac first – Roy Ling Sep 22 '16 at 05:56
  • 2
    To add to this, the use of find is unnecessary if you're only talking about files in a single directory. It's always best to keep it as simple as possible, so you can just do `rename 's/^123_//' *` and it'll do the exact same thing. Cheers. – HodorTheCoder Jul 26 '17 at 14:51
  • 3
    @HodorTheCoder And if you've got `globstar` set in bash (`shopt -s globstar`), you can do it recursively: `rename 's/^123_//' **/*` – Andrew Keeton Sep 27 '19 at 21:40
  • @HodorTheCoder: The title of the question asks for a solution for a directory recursively. – user unknown Sep 28 '19 at 09:04
  • FYI, it is possible that one's computer has the util-linux rename command installed and not the Perl lib rename, the syntax is different between the two so check the help output. The util-linux rename can be made to work, but seems to only support find and rename of literal text strings. Also it will only rename the first occurrence of the match string in a file name, so you may need to run it multiple times. – Evan Cox Aug 10 '22 at 21:36
17

In case you want to replace string in file name called foo to bar you can use this in linux ubuntu, change file type for your needs

find -name "*foo*.filetype" -exec rename 's/foo/bar/' {} ";"
talsibony
  • 8,448
  • 6
  • 47
  • 46
  • 2
    This is the most usable generalised answer – chasmani Jul 07 '16 at 20:09
  • 1
    This really worked and is simple. How can I make this replace names of files in directories recursively. – madu Oct 20 '18 at 19:00
  • @madu Find searches recursively into directories by default. If you wanted it to only search the current directory, you would add a `-maxdepth 1` flag before the `-name …` flag. – Slipp D. Thompson Aug 23 '22 at 22:35
8

you could check 'rename' tool

for example

rename 's/^123_//' *.txt

or (gawk is needed)

find . -name '123_*.txt'|awk '{print "mv "$0" "gensub(/\/123_(.*\.txt)$/,"/\\1","g");}'|sh

test:

kent$  tree
.
|-- 123_a.txt
|-- 123_b.txt
|-- 123_c.txt
|-- 123_d.txt
|-- 123_e.txt
`-- u
    |-- 123_a.txt
    |-- 123_b.txt
    |-- 123_c.txt
    |-- 123_d.txt
    `-- 123_e.txt

1 directory, 10 files

kent$  find . -name '123_*.txt'|awk '{print "mv "$0" "gensub(/\/123_(.*\.txt)$/,"/\\1","g");}'|sh

kent$  tree
.
|-- a.txt
|-- b.txt
|-- c.txt
|-- d.txt
|-- e.txt
`-- u
    |-- a.txt
    |-- b.txt
    |-- c.txt
    |-- d.txt
    `-- e.txt

1 directory, 10 files
Kent
  • 189,393
  • 32
  • 233
  • 301
  • 1
    You should mention the homonym trap: you talk about special rename command that usurps the name of rename from util-linux (which does not deal with regular expressions). – daxim Feb 22 '12 at 11:35
  • The `rename` example isn't recursive. The `find` example is, but it needlessly doesn't use `rename` for renaming. And it pipes a generated script to `sh`, which is icky. – sorpigal Feb 22 '12 at 12:28
  • Thanks, is it possible for me to modify the filename to all lower cases after the rename? i mean.. any Xxx.txt should be xxx.txt finally? – user1225606 Feb 23 '12 at 05:00
5

To expand on Sorpigal's answer, if you want to replace the 123_ with hello_, you could use

    find . -name '123*.txt' -type f -exec bash -c 'mv "$1" "${1/\/123_/\/hello_}"' -- {} \;
Community
  • 1
  • 1
Pat O
  • 113
  • 2
  • 7
4

A slight variation on Kent's that doesn't require gawk and is a little bit more readable, (although, thats debatable..)

find . -name "123*" | awk '{a=$1; gsub(/123_/,""); printf "mv \"%s\" \"%s\"\n", a, $1}' | sh

synthesizerpatel
  • 27,321
  • 5
  • 74
  • 91
3

Using rename from util-linux 2.28.2 I had to use a different syntaxt:

find -name "*.txt" -exec rename -v "123_" ""  {} ";" 
Paulo Fidalgo
  • 21,709
  • 7
  • 99
  • 115
2

Provided you don't have newlines in your filenames:

find -name '123_*.txt' | while IFS= read -r file; do mv "$file" "${file#123_}"; done

For a really safe way, provided your find supports the -print0 flag (GNU find does):

find -name '123_*.txt' -print0 | while IFS= read -r -d '' file; do mv "$file" "${file#123_}"; done
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
1

You can make a little bash script for that. Create a file named recursive_replace_filename with this content :

#!/bin/bash

if test $# -lt 2; then
  echo "usage: `basename $0` <to_replace> <replace_value>"
fi

for file in `find . -name "*$1*" -type f`; do
  mv "'$file'" "${file/'$1'/'$2'}"
done

Make executable an run:

$ chmod +x recursive_replace_filename
$ ./recursive_replace_filename 123_ ""

Keep note that this script can be dangerous, be sure you know what it's doing and in which folder you are executing it, and with which arguments. In this case, all files in the current folder, recursively, containing 123_ will be renamed.

BDL
  • 21,052
  • 22
  • 49
  • 55
Rivenfall
  • 1,189
  • 10
  • 15
1

Tried the answer above but it didn't work for me cause i had the string inside folders and files name at the same time so here is what i did the following bash script:

  for fileType in d f
  do
    find  -type $fileType -iname "stringToSearch*" |while read file
    do
      mv $file $( sed -r "s/stringToSearch/stringToReplaceWith/" <<< $file )
    done
  done

First i began by replacing inside folders name then inside files name.

Venom
  • 1,010
  • 10
  • 20
  • Worked well for me with spaces in the names of files. Just make sure you quote both vars in the mv line to work with spaces – Kelly Bang Aug 05 '21 at 22:28
1

Here's the only solution to date that works:

find-rename-recursive --pattern '123_' --string '' -- . -type f -name "123_*"

All other solutions don't work for me--some even deleted files!

For details, see https://github.com/l3x/helpers#find-rename-recursive

l3x
  • 30,760
  • 1
  • 55
  • 36
0

If the names are fixed you can visit each directory and perform the renaming in a subshell (to avoid changing the current directory) fairly simply. This is how I renamed a bunch of new_paths.json files each to paths.json:

for file in $(find root_directory -name new_paths.json)
  do
     (cd $(dirname $file) ; mv new_paths.json paths.json)
  done
holdenweb
  • 33,305
  • 7
  • 57
  • 77
0

using the examples above, i used this to replace part of the file name... this was used to rename various data files in a directory, due to a vendor changing names.

find . -name 'o3_cloudmed_*.*' -type f -exec bash -c 'mv "$1" "${1/cloudmed/revint}"' -- {} \;

0

Below, the find command searches recursively in the tree from the directory passed as first parameter (here it is the current directory "."). The following parameters are predicates to filter the files we are looking for. The "-name" predicate is a pattern to which the current file name must match (here we want manage only filenames beginning with '123_' and terminating with '.txt'. The "-type" filters the type of the file ('f' means regular file, 'd' would mean directory...). The "-exec" predicate runs a command line into which '{}' is the current file name and the ';' terminates the command.

find . -name '123_*.txt' -type f -exec bash -c 'name={};mv $name ${name//123_//}' \;
Rachid K.
  • 4,490
  • 3
  • 11
  • 30
bo he
  • 13
  • 3
-2
prename s/^123_// 123_*

See prename in the official Perl5 wiki. Copy for your convenience:

prename - A script that renames files according to a regular expression. (Where was the original published?) Originally named rename, it is found mostly as prename because the original one clashes with the rename command from the util-linux package. Numerous forks and reimplementations:

daxim
  • 39,270
  • 4
  • 65
  • 132