Rename multiple files, but only rename part of the filename in Bash - bash

I know how I can rename files and such, but I'm having trouble with this.
I only need to rename test-this in a for loop.
test-this.ext
test-this.volume001+02.ext
test-this.volume002+04.ext
test-this.volume003+08.ext
test-this.volume004+16.ext
test-this.volume005+32.ext
test-this.volume006+64.ext
test-this.volume007+78.ext

If you have all of these files in one folder and you're on Linux you can use:
rename 's/test-this/REPLACESTRING/g' *
The result will be:
REPLACESTRING.ext
REPLACESTRING.volume001+02.ext
REPLACESTRING.volume002+04.ext
...
rename can take a command as the first argument. The command here consists of four parts:
s: flag to substitute a string with another string,
test-this: the string you want to replace,
REPLACESTRING: the string you want to replace the search string with, and
g: a flag indicating that all matches of the search string shall be replaced, i.e. if the filename is test-this-abc-test-this.ext the result will be REPLACESTRING-abc-REPLACESTRING.ext.
Refer to man sed for a detailed description of the flags.

Use rename as shown below:
rename test-this foo test-this*
This will replace test-this with foo in the file names.
If you don't have rename use a for loop as shown below:
for i in test-this*
do
mv "$i" "${i/test-this/foo}"
done

Function
I'm on OSX and my bash doesn't come with rename as a built-in function. I create a function in my .bash_profile that takes the first argument, which is a pattern in the file that should only match once, and doesn't care what comes after it, and replaces with the text of argument 2.
rename() {
for i in $1*
do
mv "$i" "${i/$1/$2}"
done
}
Input Files
test-this.ext
test-this.volume001+02.ext
test-this.volume002+04.ext
test-this.volume003+08.ext
test-this.volume004+16.ext
test-this.volume005+32.ext
test-this.volume006+64.ext
test-this.volume007+78.ext
Command
rename test-this hello-there
Output
hello-there.ext
hello-there.volume001+02.ext
hello-there.volume002+04.ext
hello-there.volume003+08.ext
hello-there.volume004+16.ext
hello-there.volume005+32.ext
hello-there.volume006+64.ext
hello-there.volume007+78.ext

Without using rename:
find -name test-this\*.ext | sed 'p;s/test-this/replace-that/' | xargs -d '\n' -n 2 mv
The way it works is as follows:
find will, well, find all files matching your criteria. If you pass -name a glob expression, don't forget to escape the *.
Pipe the newline-separated* list of filenames into sed, which will:
a. Print (p) one line.
b. Substitute (s///) test-this with replace-that and print the result.
c. Move on to the next line.
Pipe the newline-separated list of alternating old and new filenames to xargs, which will:
a. Treat newlines as delimiters (-d '\n').
b. Call mv repeatedly with up to 2 (-n 2) arguments each time.
For a dry run, try the following:
find -name test-this\*.ext | sed 'p;s/test-this/replace-that/' | xargs -d '\n' -n 2 echo mv
*: Keep in mind it won't work if your filenames include newlines.

to rename index.htm to index.html
rename [what you want to rename] [what you want it to be] [match on these files]
rename .htm .HTML *.htm
renames index.htm to index.html
It will do this for all files that match *.htm in the folder.

thx for your passion and answers. I also find a solution for me to rename multiple files on my linux terminal and directly add a little counter. With this I have a very good chance to have better SEO names.
Here is the command
count=1 ; zmv '(*).jpg' 'new-seo-name--$((count++)).jpg'
I also do a live coding video and publush it to YouTube

Related

Displaying file name using special characters

I've one file ABC_123.csv in my app directory and I want to display its full name. I found two ways to do it (see below code snippet): one using ??? and the other using asterisk at the end of required text ABC_.
But, both ways are also displaying the path along with the name. Both below commands are producing results in this format: path + name. I only need the name. Is there any special character (like ? or *) to display the name file only?
[input]$ ls /usr/opt/app/ABC_???.csv
[output] /usr/opt/app/ABC_123.csv
[input]$ ls /usr/opt/app/ABC_*.csv
[output] /usr/opt/app/ABC_123.csv
I cannot do this:
[input]$ cd /usr/opt/app
[input]$ ls ABC_???.csv
[output] ABC_123.csv
Required output:
[input]$ ls /usr/opt/app/ABC_(some-special-character).csv
[output] ABC_123.csv
[Edited] basename is working, but, I want to achieve this using ls and some special character (as highlighted above in Required output). Is there any way to this?
[input]$ basename /usr/opt/app/ABC_???.csv
[output] ABC_123.csv
You can use
find /usr/opt/app/ -type f -name "ABC_*.csv" -exec basename '{}' \;
basename will isolate the file name. find will search the specified directory for files that match the provided pattern.
If you were limited to ls, then this might help.
file="$(echo /usr/opt/app/ABC_???.csv)"; echo "${file##*/}"
Pipe every csv file to basename:
ls /usr/opt/app/ABC_*.csv | xargs basename -a
Without a need for basename:
(cd /usr/opt/app/ && ls ABC_*.csv)
"I cannot do this" was similar but didn't explain why, so maybe one-liner is doable. Doing in sub-shell prevents current dir from changing.
And no - there is no special character that could be used there. It's globbing: https://en.wikipedia.org/wiki/Glob_(programming)
Pure bash:
files=( /usr/opt/app/ABC_???.csv ) # Expands to array of matching filename(s)
printf "%s\n" "${file[#]##*/}"
The ## part of the expansion removes the longest leading string that matches the following pattern from each element of the array (So it works if this pattern matches only a single file like your question says, or if it matches more than one). */ will thus cut off everything up to and including the last slash in the string.

Sed & Mac OS Terminal: How to remove parentheses content from the first line of every file?

I am on Mac Os 10.14.6 and have a directory that contains subdirectories that all contain text files. Altogether, there are many hundreds of text files.
I would like to go through the text files and check for any content in the first line that is in parentheses. If such content is found, then the parentheses (and content in the parentheses) should be removed.
Example:
Before removal:
The new world (82 edition)
After removal:
The new world
How would I do this?
Steps I have tried:
Google around, it seems SED would be best for this.
I have found this thread, which provides SED code for removing bracketed content.
sed -e 's/([^()]*)//g'
However, I am not sure how to adapt it to work on multiple files and also to limit it to the first line of those files. I found this thread which explains how to use SED on multiple files, but I am not sure how to adapt the example to work with parentheses content.
Please note: As long as the solution works on Mac OS terminal, then it does not need to use SED. However, from Googling, SED seems to be the most suited.
I managed to achieve what you're after simply by using a bash script and sed together, as so:
#!/bin/bash
for filename in $PWD/*.txt; do
sed -i '' '1 s/([^()]*)//g' $filename
done
The script simply iterates over all the .txt files in $PWD (the current working directory, so that you can add this script to your bin and run it anywhere), and then runs the command
sed -ie '1 s/([^()]*)//g' $filename
on the file. By starting the command with the number 1 we tell sed to only work on the first line of the file :)
Edit: Best Answer
The above works fine in a directory where all contained objects are files, and not including directories; in other words, the above does not perform recursive search through directories.
Therefore, after some research, this command should perform exactly what the question asks:
find . -name "*.txt" -exec sed -i '' '1 s/([^()]*)//g' {} \;
I must iterate, and reiterate, that you test this on a backup first to test it works. Otherwise, use the same command as above but change the '' in order to control the creation of backups. For example,
find . -name "*.txt" -exec sed -i '.bkp' '1 s/([^()]*)//g' {} \;
This command will perform the sed replace in the original file (keeping the filename) but will create a backup file for each with the appended .bkp, for example test1.txt becomes test1.txt.bkp. This a safer option, but choose what works best for you :)
Good try,
The command you where looking for single line:
sed -E '1s|\([^\)]+\)||'
The command to replace each input file first line:
sed -Ei '1s|\([^\)]+\)||' *.txt
example:
echo "The new world (82 edition)" |sed -E '1s|\([^\)]+\)||'
The new world
Explanation
sed -Ei E option: the extended RegExp syntax, i option: for in-place file replacement
sed -Ei '1s|match RegExp||' for first line only, replace first matched RegExp string with empty string
\([^\)]+\) RegExp matching: start with (, [^\)]any char not ), + - more than once, terminate with )
Try:
# create a temporary file
tmp=$(mktemp)
# for each something in _the current directory_
for i in *; do
# if it is not a file, don't parse it
if [ ! -f "$i" ]; then continue; fi
# remove parenthesis on first line, save the output in temporary file
sed '1s/([^)]*)//g' "$i" > "$tmp"
# move temporary file to the original file
mv "$tmp" "$i"
done
# remove temporary file
rm "$tmp"

bash change absolute path in file line by line for script creation

I'm trying to create a bash script based on a input file (list.txt). The input File contains a list of files with absolute path. The output should be a bash script (move.sh) which moves the files to another location, preserve the folder structure, but changing the target folder name slightly before.
the Input list.txt File example looks like this :
/In/Folder_1/SomeFoldername1/somefilename_x.mp3
/In/Folder_2/SomeFoldername2/somefilename_y.mp3
/In/Folder_3/SomeFoldername3/somefilename_z.mp3
The output file (move.sh) should looks like this after creation :
mv "/In/Folder_1/SomeFoldername1/somefilename_x.mp3" /gain/Folder_1/
mv "/In/Folder_2/SomeFoldername2/somefilename_y.mp3" /gain/Folder_2/
mv "/In/Folder_3/SomeFoldername3/somefilename_z.mp3" /gain/Folder_3/
The folder structure should be preserved, more or less.
after executing the created bash script (move.sh), the result should looks like this :
/gain/Folder_1/somefilename_x.mp3
/gain/Folder_2/somefilename_y.mp3
/gain/Folder_3/somefilename_z.mp3
What I've done so far.
1. create a list of files with absolute path
find /In/ -iname "*.mp3" -type f > /home/maars/mp3/list.txt
2. create the move.sh script
cp -a /home/maars/mp3/list.txt /home/maars/mp3/move.sh
# read the list and split the absolute path into fields
while IFS= read -r line;do
fields=($(printf "%s" "$line"|cut -d'/' --output-delimiter=' ' -f1-))
done < /home/maars/mp3/move.sh
# add the target path based on variables at the end of the line
sed -i -E "s|\.mp3|\.mp3"\"" /gain/"${fields[1]}"/|g" /home/maars/mp3/move.sh
sed -i "s|/In/|mv "\""/In/|g" /home/maars/mp3/move.sh
The script just use the value of ${fields[1]}, which is Folder_1 and put this in all lines at the end. Instead of Folder_2 and Folder_3.
The current result looks like
mv "/In/Folder_1/SomeFoldername1/somefilename_x.mp3" /gain/Folder_1/
mv "/In/Folder_2/SomeFoldername2/somefilename_y.mp3" /gain/Folder_1/
mv "/In/Folder_3/SomeFoldername3/somefilename_z.mp3" /gain/Folder_1/
rsync is not an option since I need the full control of files to be moved.
What could I do better to solve this issue ?
EDIT : #Socowi helped me a lot by pointing me in the right direction. After I did a deep dive into the World of Regex, I could solve my Issues. Thank you very much
The script just use the value of ${fields[1]}, which is Folder_1 and put this in all lines at the end. Instead of Folder_2 and Folder_3.
You iterate over all lines and update fields for every line. After you finished the loop, fields retains its value (from the last line). You would have to move the sed commands into your loop and make sure that only the current line is replaced by sed. However, there's a better way – see down below.
What could I do better
There are a lot of things you could improve, for instance
Creating the array fields with mapfile -d/ fields instead of printf+cut+($()). That way, you also wouldn't have problems with spaces in paths.
Use sed only once instead of creating the array fields and using multiple sed commands. You can replace step 2 with this small script:
cp -a /home/maars/mp3/list.txt /home/maars/mp3/move.sh
sed -i -E 's|^/[^/]*/([^/]*).*$|mv "&" "/gain/\1"|' /home/maars/mp3/move.sh
However, the best optimization would be to drop that three step approach and use only one script to find and move the files:
find /In/ -iname "*.mp3" -type f -exec rename -n 's|^/.*?/(.*?)/.*/(.*)$|/gain/$1/$2|' {} +
The -n option will print what will be renamed without actually renaming anything . Remove the -n when you are happy with the result. Here is the output:
rename(/In/Folder_1/SomeFoldername1/somefilename_x.mp3, /gain/Folder_1/somefilename_x.mp3)
rename(/In/Folder_2/SomeFoldername2/somefilename_y.mp3, /gain/Folder_2/somefilename_y.mp3)
rename(/In/Folder_3/SomeFoldername3/somefilename_z.mp3, /gain/Folder_3/somefilename_z.mp3)
It's not builtin to bash, but the mmv command is nice for this kind of mv where you need to use wildcards in paths. Something like the following should work:
mmv "in/*/*/*" "#1/#3"
Note that this won't create the directories for you - but in your example above it looks like these already exist?

How will i use sed shell script for replacing a pattern in a list of file

I have some 100 files which have my name in it, RAHUL. I want it replaced with another term RAHUL2. I have a file which contains the list of files and i want to fetch it in a sed to do the changes.
files :
C:/desktop/file1.txt
C:/desktop/rahul/file1.txt
C:/desktop/rahul/file3.txt
C:/desktop/rahul/file4.txt
C:/desktop/rahul/file6.txt
C:/desktop/rahul/file8.txt
C:/desktop/rahul/file9.txt
and in each file data, i want to replace all occurance of term RAHUL with RAHUL2
I assume you are using some Cygwin environment on Windows, since you have sed. Then you can use find to list all the files and execute sed on them:
find C:/desktop -type f -name 'file*.txt' -exec sed -i 's/RAHUL/RAHUL2/g' {} \;
Please make a backup of the original files if you are using sed -i but aren't sure if the command is working already. This because sed -i will overwrite the original file. You have been warned.
hek2mgl's answer is good if you want to search for all files matching file*.txt pattern on your C:/desktop directory.
Now as you already have a file containing the list of files to edit, here is another way to proceed:
while read FILE ; do sed -i 's/RAHUL/RAHUL2/g' $FILE ; done < files_to_edit.txt
The read command will read your input one line after another. Input is files_to_edit.txt file, as indicated by the < input redirection operator.
Remark about -i option remains valid: it will edit your files in place, so make backup, or at least run it first on a couple of files (potentially without -i option, just to check what output is).

Shell scripting - running a regex to modify a file in place, that includes the file name

I have many html files named 12345.html, 12346.html, etc. I need to change "style.css" to "style-12345.css" or the appropriate file name. I'm not sure what tool to use, recommendations?
This is pretty easily done with a for loop and sed:
for i in *.html; do
sed -i "s/style\\.css/style-`basename $i .html`.css/g" $i
done
The loop runs the inner command with $i set to each .html filename. sed -i modifies the file in place. basename $i .html gets $i without the .html suffix (i.e. just the number)
Look for a command named rename. It comes in two varieties, depending upon the implementation.
The perl package provides /usr/bin/prename which uses perl-style regular expressions to rename files. As an example, this command
$ prename 's/foo/bar/ *foo*
would change 'foo' to 'bar' in every filename that contains 'foo'.
The util-linux package provides /usr/bin/rename which uses simple string substitution to rename files. As an example, this command
$ rename foo bar *foo*
would have the same effect as the first one.
prename is much more powerful than regular rename, but that power means it's trickier to use.
perl -pi -e 's/style.css/style-12345.css/g' *.html

Resources