First, I should say that the easiest way to do this is to use the prename or rename commands.
On Ubuntu, OSX (Homebrew package rename, MacPorts package p5-file-rename), or other systems with perl rename (prename):
Copyrename s/0000/000/ F0000*
or on systems with rename from util-linux-ng, such as RHEL:
Copyrename 0000 000 F0000*
That's a lot more understandable than the equivalent sed command.
But as for understanding the sed command, the sed manpage is helpful. If you run man sed and search for & (using the / command to search), you'll find it's a special character in s/foo/bar/ replacements.
Copy s/regexp/replacement/
Attempt to match regexp against the pattern space. If success‐
ful, replace that portion matched with replacement. The
replacement may contain the special character & to refer to that
portion of the pattern space which matched, and the special
escapes \1 through \9 to refer to the corresponding matching
sub-expressions in the regexp.
Therefore, \(.\) matches the first character, which can be referenced by \1.
Then . matches the next character, which is always 0.
Then \(.*\) matches the rest of the filename, which can be referenced by \2.
The replacement string puts it all together using & (the original
filename) and \1\2 which is every part of the filename except the 2nd
character, which was a 0.
This is a pretty cryptic way to do this, IMHO. If for some reason the rename command was not available and you wanted to use sed to do the rename (or perhaps you were doing something too complex for rename?), being more explicit in your regex would make it much more readable. Perhaps something like:
Copyls F00001-0708-*|sed 's/F0000\(.*\)/mv & F000\1/' | sh
Being able to see what's actually changing in the s/search/replacement/ makes it much more readable. Also it won't keep sucking characters out of your filename if you accidentally run it twice or something.
Answer from Edward Anderson on Stack OverflowFirst, I should say that the easiest way to do this is to use the prename or rename commands.
On Ubuntu, OSX (Homebrew package rename, MacPorts package p5-file-rename), or other systems with perl rename (prename):
Copyrename s/0000/000/ F0000*
or on systems with rename from util-linux-ng, such as RHEL:
Copyrename 0000 000 F0000*
That's a lot more understandable than the equivalent sed command.
But as for understanding the sed command, the sed manpage is helpful. If you run man sed and search for & (using the / command to search), you'll find it's a special character in s/foo/bar/ replacements.
Copy s/regexp/replacement/
Attempt to match regexp against the pattern space. If success‐
ful, replace that portion matched with replacement. The
replacement may contain the special character & to refer to that
portion of the pattern space which matched, and the special
escapes \1 through \9 to refer to the corresponding matching
sub-expressions in the regexp.
Therefore, \(.\) matches the first character, which can be referenced by \1.
Then . matches the next character, which is always 0.
Then \(.*\) matches the rest of the filename, which can be referenced by \2.
The replacement string puts it all together using & (the original
filename) and \1\2 which is every part of the filename except the 2nd
character, which was a 0.
This is a pretty cryptic way to do this, IMHO. If for some reason the rename command was not available and you wanted to use sed to do the rename (or perhaps you were doing something too complex for rename?), being more explicit in your regex would make it much more readable. Perhaps something like:
Copyls F00001-0708-*|sed 's/F0000\(.*\)/mv & F000\1/' | sh
Being able to see what's actually changing in the s/search/replacement/ makes it much more readable. Also it won't keep sucking characters out of your filename if you accidentally run it twice or something.
You've had your sed explanation. Now you can use just the shell. No need for external commands.
Copyfor file in F0000*
do
echo mv "$file" "${file/#F0000/F000}"
# ${file/#F0000/F000} means replace the pattern that starts at beginning of string
done
Note that this snippet runs echo as a safety measure that prints what the mv command will do without doing it. To actually perform the mv, you need to remove echo.
To replace # by somethingelse for filenames in the current directory (not recursive) you can use the (Perl-)rename utility:
rename 's/#/somethingelse/' *
Characters like - must be escaped with a \.
For your case, you would want to use
rename 's/#U00a9/safe/g' *
Note that if you only want to operate on a certain selection of files, e.g., only *.jpg, adjust the final input to match that selection:
rename 's/#U00a9/safe/g' *.jpg
To perform a test before actually changing filenames, use the -n flag:
demo/> ls
Lucky-#U00a9NBC-125x125.jpg
Lucky-#U00a9NBC-150x150.jpg
demo/> rename -n 's/#U00a9/safe/g' *.jpg
rename(Lucky-#U00a9NBC-125x125.jpg, Lucky-safeNBC-125x125.jpg)
rename(Lucky-#U00a9NBC-150x150.jpg, Lucky-safeNBC-150x150.jpg)
For OS X, rename can be installed using homebrew: brew install rename.
This is not hard, simply make sure to escape the octothorpe (#) in the name by prepending a reverse-slash (\).
find . -type f -name 'Lucky-*' | while read FILE ; do
newfile="$(echo ${FILE} |sed -e 's/\\#U00a9/safe/')" ;
mv "${FILE}" "${newfile}" ;
done
How to rename multiple files with sed?
linux - Rename multiple files in directory - Using sed or rename command - Unix & Linux Stack Exchange
Renaming files with sed
sed to rename files in a folder - please help with script
There are a few issues here. First of all, your files are not in /00101234/, / is the root directory, kinda like Windows's C:. Your files are in ~/Desktop/images/00101234/ which means /home/yourUserName/Desktop/images/ (where yourUserName is your user name). The easiest way to deal with this, therefore, is to use relative paths. For example, consider this file:
/dir1/dir2/file
That's the absolute path to file. But if you are inside the dir1 directory, you can use a path that's relative to your current location: dir2/file.
With this in mind, let's have another look at your csv file:
/00101234/1101.jpg,/Jewellery/ALittleThankYouTeacher1101.jpg
These are relative paths. You can deal with this in two ways:
- Move into the
~/Desktop/imagesdirectory and use the paths as they are. - Convert them to absolute paths.
I will focus on 2 since it is less likely to break. This command will not actually do anything, but it will print out the list of actions to be performed (run this from the directory containing your csv file and change yourCsv.csv to the actual name of your file):
while IFS=, read -r old new; do
echo mv "~/Desktop/images${old}" "~/Desktop/images/Jewlery${new}"
done < yourCsv.csv
On my system, using the file you provided, this prints:
mv ~/Desktop/images/00101234/1101.jpg ~/Desktop/images/Jewlery/Jewellery/ALittleThankYouTeacher1101.jpg
mv ~/Desktop/images/00101234/1102.jpg ~/Desktop/images/Jewlery/Jewellery/ALittleThinkingOfYou1102.jpg
mv ~/Desktop/images/00101234/1155.jpg ~/Desktop/images/Jewlery/Jewellery/ALittleDreamcatcher1155.jpg
mv ~/Desktop/images/00101234/1203.jpg ~/Desktop/images/Jewlery/Jewellery/ALittleLuckyElephant1203.jpg
If that prints what you want, then we're ready to go. Remove the echo (that just means "print this", so removing it will cause the loop to execute the mv command instead of just printing it).
However, and this is important, your csv is probably a bit different. I am assuming you created it in Windows, which means it will have different line endings. So, to be on the safe side, you want to run this:
tr -d '\r' < yourCsv.csv | while IFS=, read -r old new; do
mv "~/Desktop/images${old}" "~/Desktop/images/Jewlery${new}"
done < yourCsv.csv
Of course, I strongly recommend that you first make a backup of all of these files just in case something goes wrong.
This is an XY-problem, and at the moment the solution to problem X is not clear, but the solution to problem Y is:
To prevent read interpreting backslashes, use option -r. From help read:
-r do not allow backslashes to escape any characters
Example
Setup:
$ echo '\00101191\XYZ123.jpg,\Homeware\TravelMugXYZ123.jpg' > files.csv
Without -r:
$ while IFS=, read orig new; do echo "$orig" "$new"; done < files.csv
00101191XYZ123.jpg HomewareTravelMugXYZ123.jpg
With -r:
$ while IFS=, read -r orig new; do echo "$orig" "$new"; done < files.csv
\00101191\XYZ123.jpg \Homeware\TravelMugXYZ123.jpg
Sample files as follow; I just want to rename
-tmpto.tmp
$ ls -1 *tmp file-3-tmp file-4-tmp
2. 1st attempt with sed
$ for i in *tmp; do echo $i | sed 's/-t/.t/'; done file-3.tmp file-4.tmp
Looks promising, but this is just echo, not the actual file. Same output as step 1. So I use sed -i but still didn't work
$ for i in *tmp; do sed -i 's/-t/.t/' $i; done $ ls -1 *tmp file-3-tmp file-4-tmp
Let me know the right way to do this. Thanks
Using Perl's rename (usable in any OS):
rename -n 's/TEST/PROD/g' ./*TEST*.XML
Remove -n switch, aka dry-run when your attempts are satisfactory to rename for real.
Which rename are you using? With
$ rename --version
rename from util-linux 2.39.3
Starting with:
$ ls -1
PAGES_TEST_SART1.XML
PAGES_TEST_SART2.XML
PAGES_TEST_SART3.XML
PAGES_TEST_SART4.XML
PAGES_TEST_SART5.XML
PAGES_TEST_SART6.XML
PAGES_TEST_SART7.XML
PAGES_TEST_SART8.XML
PAGES_TEST_SART9.XML
You can do:
$ rename TEST PROD *
and get:
$ ls -1
PAGES_PROD_SART1.XML
PAGES_PROD_SART2.XML
PAGES_PROD_SART3.XML
PAGES_PROD_SART4.XML
PAGES_PROD_SART5.XML
PAGES_PROD_SART6.XML
PAGES_PROD_SART7.XML
PAGES_PROD_SART8.XML
PAGES_PROD_SART9.XML
If you don't have rename (it's a really short Perl script) or your rename is the more simplistic util-linux version or you simply want to know how to make your command work: you just need to send it to the shell for execution:
find spec -name "*_test.rb" -exec sh -c 'echo mv "$1" "$(echo "$1" | sed s/test.rb\$/spec.rb/)"' _ {} \;
Too complicated. If you have the rename command available, you could try the following:
find . -name "*_test.rb" -print0 | xargs -0 rename "s/_test/_spec/"
This finds the relevant files, sends them to xargs which in turn uses the rename tool (which renames files according to the given perl regex).
If you happen to have a file that looks like this:
old_name1, new_name1
old_name2, new_name2
old_name3, new_name3
You can do a dirty little trick:
sed 's/^/mv -vi "/;s/, /" "/;s/$/";/' < names.csv | bash -
The sed comamnds (delimited by semicolons) does this (s is for substitute, / is used as a delimiter, any other character would do, @ is often used as well):
s/^/mv -vi "/- addsmv -vi "at the beginning of each line (^is beginning of line);s/, /" "/- replaces comma followed by a space with" ";s/$/";/- appends double quotes to all lines ($means end of line)
and hence the output of sed will be this:
mv -vi "old_name1" "new_name1"
mv -vi "old_name2" "new_name2"
mv -vi "old_name3" "new_name3"
which will be fed to bash to execute. The quotes around filenames are not mandatory, they just guard any possible spaces in the filenames (however they will defintely not help against spaces followed by a comma, since that is used as delimiter). the -v instructs mv to display what it is doing (in case something goes wrong, you'll know what happened), -i will cause it to ask if it were to overwrite already existing file.
There are several quoting problems in your script. It will mangle file names containing special characters: whitespace, \[?*-.
**Always use double quotes around variable substitutions "$i" and command substitutions "$(sed …)". Without double quotes, the value of the variable is interpreted as a list of whitespace-separated wildcard patterns, and each pattern is replaced by the list of matching file names (if there are no matches, the pattern is left alone).
Also, if a file name begins with -, it will be interpreted as an option by mv and possibly by echo. Pass -- first to tell the command that there will be no more options.
for i in *.ai; do
mv -- "$i" "$(printf '%s\n' "$i" | sed 's,a1,b1,')"
done
(This still assumes that your file names don't contain newlines.) (I also fixed the sed argument into what you presumably meant.)
If your list of file names is plain comma-separated with no quotes, you can parse it with a shell script. This assumes that there are no commas or newlines in any file name.
while IFS=, read -r from to junk; do
mv -- "$from" "$to"
done <"$files_to_rename.csv"
If your input file is a CSV file with possible quoting, use a real CSV parser. Don't try to roll your own. See Is there a robust command line tool for processing csv files? For example, in Python (with the CSV on standard input):
import csv, os, sys
for line in csv.reader(sys.stdin):
(source, dest) = line
os.rename(source, dest)
You can use the rename command to do the renaming
rename Centos1 Centos2 *
or if you have the perl rename
rename 's/Centos1/Centos2' *
Your find command should change all occurences of Centos1 to Centos2 so you could wrap them in a simple script to make it a single command.
#!/bin/bash
rename Centos1 Centos2 *
find . -type f -exec sed -i 's/Centos1/Centos2/g' {} +
Does your answer really need to include only these commands, or can you use bash builtins and file globbing?
for file in Centos1*; do
sed -i 's/Centos1/Centos2/g' "$file"
mv "$file" "$(echo "$file" | sed 's/^Centos1/Centos2/')"
done
Edit: Using just find/sed, and renames inside files
find ./ -type f -name Centos1* -exec sh -c 'sed -i "s/Centos1/Centos2/g" "$1"; mv "$1" "$(echo "$1" | sed "s/Centos1/Centos2/")"' X '{}' \;
rename 's/ACDC/AC-DC/' *.xxx
from man rename
DESCRIPTION
"rename" renames the filenames supplied according to the rule specified as the
first argument. The perlexpr argument is a Perl expression which is expected to modify the
$_ string in Perl for at least some of the filenames specified. If a given filename is not
modified by the expression, it will not be renamed. If no filenames are given on
the command line, filenames will be read via standard input.
For example, to rename all files matching "*.bak" to strip the extension, you might say
rename 's/\.bak$//' *.bak
To translate uppercase names to lower, you'd use
rename 'y/A-Z/a-z/' *
This answer contains the good parts from all other answers, while leaving out such heresy as ls | while read.
Current directory:
for file in ACDC*.xxx; do
mv "$file" "${file//ACDC/AC-DC}"
done
Including subdirectories:
find . -type f -name "ACDC*" -print0 | while read -r -d '' file; do
mv "$file" "${file//ACDC/AC-DC}"
done
Newline characters are really unlikely to be in filenames, so this can be simpler while still working with names containing spaces:
find . -type f -name "ACDC*" | while read -r file; do
mv "$file" "${file//ACDC/AC-DC}"
done