Find and change linux

Find multiple files and rename them in Linux

I am having files like a_dbg.txt, b_dbg.txt . in a Suse 10 system. I want to write a bash shell script which should rename these files by removing «_dbg» from them. Google suggested me to use rename command. So I executed the command rename _dbg.txt .txt *dbg* on the CURRENT_FOLDER My actual CURRENT_FOLDER contains the below files.

CURRENT_FOLDER/a_dbg.txt CURRENT_FOLDER/b_dbg.txt CURRENT_FOLDER/XX/c_dbg.txt CURRENT_FOLDER/YY/d_dbg.txt 
CURRENT_FOLDER/a.txt CURRENT_FOLDER/b.txt CURRENT_FOLDER/XX/c_dbg.txt CURRENT_FOLDER/YY/d_dbg.txt 

Its not doing recursively, how to make this command to rename files in all subdirectories. Like XX and YY I will be having so many subdirectories which name is unpredictable. And also my CURRENT_FOLDER will be having some other files also.

13 Answers 13

You can use find to find all matching files recursively:

find . -iname "*dbg*" -exec rename _dbg.txt .txt '<>' \; 

EDIT: what the ‘<>‘ and \; are?

The -exec argument makes find execute rename for every matching file found. ‘<>‘ will be replaced with the path name of the file. The last token, \; is there only to mark the end of the exec expression.

All that is described nicely in the man page for find:

 -exec utility [argument . ] ; True if the program named utility returns a zero value as its exit status. Optional arguments may be passed to the utility. The expression must be terminated by a semicolon (``;''). If you invoke find from a shell you may need to quote the semicolon if the shell would otherwise treat it as a control operator. If the string ``<>'' appears anywhere in the utility name or the argu- ments it is replaced by the pathname of the current file. Utility will be executed from the directory from which find was executed. Utility and arguments are not subject to the further expansion of shell patterns and constructs. 

Источник

Find and replace filename recursively in a directory

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 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 ; done 

15 Answers 15

find . -name '123_*.txt' -type f -exec sh -c ' for f; do mv "$f" "$/$" done' sh <> + 

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

This is making use of parameter expansion 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.

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.

This is superior to the overkill awk and looping answers above for those people who have the perl rename .

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.

Читайте также:  Compile and build linux kernel

@HodorTheCoder And if you’ve got globstar set in bash ( shopt -s globstar ), you can do it recursively: rename ‘s/^123_//’ **/*

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/' <> ";" 

This really worked and is simple. How can I make this replace names of files in directories recursively.

@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.

you could check ‘rename’ tool

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 ''|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 

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).

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.

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?

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" "$"' -- <> \; 

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

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

find -name "*.txt" -exec rename -v "123_" "" <> ";" 

Provided you don’t have newlines in your filenames:

find -name '123_*.txt' | while IFS= read -r file; do mv "$file" "$"; 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" "$"; done 

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` " fi for file in `find . -name "*$1*" -type f`; do mv "'$file'" "$" done 
$ 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.

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/"  

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

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

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!

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 

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" "$"' -- <> \;

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 $' \; 

Источник

How to search and replace using grep

I need to recursively search for a specified string within all files and subdirectories within a directory and replace this string with another string. I know that the command to find it might look like this:

I don't believe grep can do this (I could be wrong). Easier ways would be to use sed or perl to do the replacing

@Eddy_Em That will replace the entire line with replace. You need to use grouping to capture the part of the line before and after the substring and then put that in the replacement line. sed -i 's/\(.*\)substring\(.*\)/\1replace\2/'

10 Answers 10

Another option is to use find and then pass it through sed.

find /path/to/files -type f -exec sed -i 's/oldstring/new string/g' <> \; 

On OS X 10.10 Terminal, a proper extension string to parameter -i is required. For example, find /path/to/files -type f -exec sed -i "" "s/oldstring/new string/g" <> \; Anyway, providing empty string still creates a backup file unlike described in manual.

Why do I get "sed: RE error: illegal byte sequence". And yes, I added the -i "" for OS X. It works otherwise.

I had the illegal byte sequence issue on macOS 10.12, and this question/answer solved my issue: stackoverflow.com/questions/19242275/….

This touches every file so file times are modified; and converts line endings from CRLF to LF on Windows.

grep -rl matchstring somedir/ | xargs sed -i 's/string1/string2/g' 

This would scan through the matching files twice. once with grep and then again with sed . Using find method is more efficient but this method you mention does work.

On OS X you will need to change sed -i 's/str1/str2/g' to sed -i "" 's/str1/str2/g' for this to work.

@cmevoli with this method, grep goes through all the files and sed only scans the files matched by grep . With the find method in the other answer, find first lists all files, and then sed will scan through all the files in that directory. So this method is not necessarily slower, it depends on how many matches there are and the differences in search speeds between sed , grep and find .

OTOH this way lets you PREVIEW what grep finds BEFORE actually replacing, reducing the risk of failure greatly, especially for regex n00bs like myself

This is also useful when your grep replacement is more clever than sed. For example ripgrep obeys .gitignore while sed doesn't.

You could even do it like this:

grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g' 

This will search for the string 'windows' in all files relative to the current directory and replace 'windows' with 'linux' for each occurrence of the string in each file.

The grep is only useful if there are files which should not be modified. Running sed on all files will update the file's modification date but leave the contents unchanged if there are no matches.

@tripleee: Be careful with . but [sed] leave the contents unchanged if there are no matches". When using -i , I believe sed changes the file time of every file it touches, even though the contents are unchanged. sed also converts line endings. I don't use sed on Windows in a Git repo because all CRLF are changed to LF .

This command needs a "" after the -i to denote that no backup files will be made after the in-place substitution takes place, at least in macosx. Check the man page for details. If you want a backup, this is where you put the extension of the file to be created.

For spaces is file names you need to do NULL termination on grep and xargs. stackoverflow.com/questions/17296525/…

This works best for me on OS X:

grep -r -l 'searchtext' . | sort | uniq | xargs perl -e "s/matchtext/replacetext/" -pi 

this is perfect! also works with ag: ag "search" -l -r . | sort | uniq | xargs perl -e 's/search/replace' -pi

Why is the sort -u even part of this? In what circumstances would you expect grep -rl to produce the same file name twice?

Usually not with grep, but rather with sed -i 's/string_to_find/another_string/g' or perl -i.bak -pe 's/string_to_find/another_string/g' .

I think this is probably the easiest method on here to get the job done. Forcing grep when it is not needed is unnecessary.

Other solutions mix regex syntaxes. To use perl/PCRE patterns for both search and replace, and process only matching files, this works quite well:

grep -rlIZPi 'match1' | xargs -0r perl -pi -e 's/match2/replace/gi;' 

match1 and match2 are usually identical but match2 can contain more advanced features that are only relevant to the substitution, e.g. capturing groups.

Translation: grep recursively and list matching filenames, each separated by null to protect any special characters; pipe any filenames to xargs which is expecting a null-separated list; if any filenames are received, pass them to perl to perform the actual substitutions.

For case-sensitive matching, drop the i flag from grep and the i pattern modifier from the s/// expression, but not the i flag from perl itself. To include binary files, remove the I flag from grep .

Источник

Оцените статью
Adblock
detector