Let's analyze your sed commands:
First sed command
sed -E 's/^(.*)$/^S01E(.*)$/g'
The substitution-regex captures everything from beginning ^ to end $ of line, using (.*). Normally sed would require you to escape the parentheses: \(.*\), but since you're using -E it's correct as printed. Next, it attempts to prefix the captured content with S01E. This where it goes wrong: The regex patterns ^ (.*) $ should not be repeated in the replacement. Instead, just put your prefix, followed by \1 which represents the first capture group (parentheses). A second capture would end up in \2 and so forth. And oh, the g modifier is not needed since there will only be a single replacement. So:
test1=`echo
/S01E\\1/'`
result for file 01-somemovie.mkv:
S01E01-somemovie.mkv
Second sed command
sed 's#^(.*)$#^S01E(.*)$#'
has a similar problem in the replacement part, plus you should escape the parentheses since it doesn't use -E. Do like this:
sed 's#^\(.*\)$#S01E\1#'
Pro tip
Since you are on Mac OSX, use Homebrew to install the utility rename. Then just rename --prepend S01E *.mkv and you're done:
$ brew install rename
$ rename -n --prepend S01E *.mkv
'01-somemovie.mkv' would be renamed to 'S01E01-somemovie.mkv'
'02-somemovie.mkv' would be renamed to 'S01E02-somemovie.mkv'
Answer from grebneke on Stack OverflowLet's analyze your sed commands:
First sed command
sed -E 's/^(.*)$/^S01E(.*)$/g'
The substitution-regex captures everything from beginning ^ to end $ of line, using (.*). Normally sed would require you to escape the parentheses: \(.*\), but since you're using -E it's correct as printed. Next, it attempts to prefix the captured content with S01E. This where it goes wrong: The regex patterns ^ (.*) $ should not be repeated in the replacement. Instead, just put your prefix, followed by \1 which represents the first capture group (parentheses). A second capture would end up in \2 and so forth. And oh, the g modifier is not needed since there will only be a single replacement. So:
test1=`echo
/S01E\\1/'`
result for file 01-somemovie.mkv:
S01E01-somemovie.mkv
Second sed command
sed 's#^(.*)$#^S01E(.*)$#'
has a similar problem in the replacement part, plus you should escape the parentheses since it doesn't use -E. Do like this:
sed 's#^\(.*\)$#S01E\1#'
Pro tip
Since you are on Mac OSX, use Homebrew to install the utility rename. Then just rename --prepend S01E *.mkv and you're done:
$ brew install rename
$ rename -n --prepend S01E *.mkv
'01-somemovie.mkv' would be renamed to 'S01E01-somemovie.mkv'
'02-somemovie.mkv' would be renamed to 'S01E02-somemovie.mkv'
I believe the lines shall be:
test1=`echo "$f" | sed 's/^\(.*\)$/^S01E\1$/'`
test2=$(echo "$f" | sed 's#^\(.*\)$#^S01E\1$#')
sed - how to rename multiple files by replacing string in file name? this string contains a "#" - Unix & Linux Stack Exchange
How to rename multiple files with sed?
macos - How to rename files using Zsh shell and `sed` command on Mac OSX - Stack Overflow
bash - How to Batch Rename Files in a macOS Terminal? - Stack Overflow
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.
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
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
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 "
(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're using zsh, you don't need to use sed: there's a zsh module called zmv that'll achieve this pretty simply:
$ ls -F -G
File-A-B.gif File-C-D.gif File-E-F.gif
$ zmv -n 'File-(*)-(*).gif' 'File-
{2}.gif'
mv -- File-A-B.gif File-AB.gif
mv -- File-C-D.gif File-CD.gif
mv -- File-E-F.gif File-EF.gif
Note: Omit -n to get it to actually run.
It works by matching things inside brackets. In this case: File-(*)-(*).gif will both match File-A-B.gif and also save A and B so we can refer to them later. Then, we move the file we've just matched to a new filename, omitting the hyphen between the two letters and inserting the letters using references: File-. {2}.gif
It's quite a powerful little module, provided you can give it the two regexes: one to match files to rename, and the second to match the renamed-file name.
The following should work for you:
ls File-* | sed 's/\(.*\)-\(.*\)/mv & \1\2/' | sh
What you tried
ls File-* | sed '' 's/\(File-[^-]\)-\(.+\)/mv & \1\2/' | sh
was pretty close. Saying:
ls File-* | sed '' 's/\(File-[^-]*\)-\(.*\)/mv & \1\2/' | sh
instead would have worked.
In your specific case you can use the following bash command (bash is the default shell on macOS):
for f in *.png; do echo mv "$f" "${f/_*_/_}"; done
Note: If there's a chance that your filenames start with -, place -- before them[1]:
mv -- "$f" "${f/_*_/_}"
Note: echo is prepended to mv so as to perform a dry run. Remove it to perform actual renaming.
You can run it from the command line or use it in a script.
"${f/_*_/_}"is an application ofbashparameter expansion: the (first) substring matching pattern_*_is replaced with literal_, effectively cutting the middle token from the name.- Note that
_*_is a pattern (a wildcard expression, as also used for globbing), not a regular expression (to learn about patterns, runman bashand search forPattern Matching).
If you find yourself batch-renaming files frequently, consider installing a specialized tool such as the Perl-based rename utility.
On macOS you can install it using popular package manager Homebrew as follows:
brew install rename
Here's the equivalent of the command at the top using rename:
rename -n -e 's/_.*_/_/' *.png
Again, this command performs a dry run; remove -n to perform actual renaming.
- Similar to the
bashsolution,s/.../.../performs text substitution, but - unlike inbash- true regular expressions are used.
[1] The purpose of special argument --, which is supported by most utilities, is to signal that subsequent arguments should be treated as operands (values), even if they look like options due to starting with -, as Jacob C. notes.
To rename files, you can use the rename utility:
brew install rename
For example, to change a search string in all filenames in current directory:
rename -nvs searchword replaceword *
Remove the 'n' parameter to apply the changes.
More info: man rename
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
You can use find's exec option to (recursively) loop over all files, then use simple string replacements:
find . -type f -name "product-103*" \
-exec sh -c 'echo mv "$0" "${0/103/104}"' '{}' \;
Remove the echo when you're sure this does what you want.
Basically, what exec does is substitute the file name of every file found in {}, which is passed as an argument to sh -c. This argument is available as $0, thus the file name. We use this $0 argument in a mv call, where the second argument is the new file name. Here, 103 is replaced with 104. Note that double quotes are needed to correctly handle whitespace in the file names.
See String Manipulation in the Bash Scripting Guide.
With zsh's zmv (on OS X through /bin/zsh):
autoload -U zmv
zmv -Wn '*103*' '*104*'
Remove the -n option when this does what you need.
Perl is better for this.
find . -name 'product-103*' | perl -nle '($new=$_) =~ s/103/104/;rename $_,$new'
The following piece of code
perl -nle '($new=$_) =~ s/103/104/;rename $_,$new'
will eventually be expanded to
while (<>) {
chomp;
($new=$_) =~ s/103/104/;
rename $_, $new;
}
The <> operator will get each file name with an '\n' at the end and save it to $_, then chomp will chop the '\n' off. Then variable $new gets the old filename from $_ and replace 103 to 104. At last, rename just rename the file in question to its new name.
For more details about the -n -l -e switch, refer to the perlrun part of perldoc.
Hi - I'd like to batch find & replace rename 2000 CSV files in a folder via Terminal. Is there a bash command for this?
I want to find all of the slashes / and replace with an underscore _.
Screenshot here: https://imgur.com/a/ofT00gm
Thanks for the help!
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