This question already has answers here:
Rename multiple files, but only rename part of the filename in Bash
(6 answers)
Closed 4 years ago.
I have the following file names where I am trying to relabel v5.4b to v5.7:
v5.4b_lvl-1.e8974326
v5.4b_lvl-1.o8974326
v5.4b_lvl-1.pe8974326
v5.4b_lvl-1.po8974326
v5.4b_lvl-2.1.e8974303
v5.4b_lvl-2.1.o8974303
v5.4b_lvl-2.1.pe8974303
v5.4b_lvl-2.1.po8974303
v5.4b_lvl-2.2.e8974304
v5.4b_lvl-2.2.o8974304
v5.4b_lvl-2.2.pe8974304
v5.4b_lvl-2.2.po8974304
v5.4b_lvl-3.1.e8974305
v5.4b_lvl-3.1.o8974305
v5.4b_lvl-3.1.pe8974305
v5.4b_lvl-3.1.po8974305
v5.4b_lvl-4.1.e8974327
v5.4b_lvl-4.1.o8974327
v5.4b_lvl-4.1.pe8974327
v5.4b_lvl-4.1.po8974327
I can't do mv v5.4b_* v5.7_* because it thinks v5.7_* is a directory so I am trying a for-loop but I can't get it to work
I am trying the recommended answer from this SO post How to set a variable to the output of a command in Bash? but getting a bunch of empty lines.
What am I doing incorrectly? How can I save the output of cut to SUFFIX so I can mv $i v5.7_$SUFFIX?
-bash-4.1$ for i in v5.4b*; do echo $i | SUFFIX=`cut -f2 -d'_'`; echo ${SUFFIX}; done
You've got echo $i in the wrong place. The output of that command needs to be piped to cut for it to read anything, then the result is assigned to SUFFIX:
for i in v5.4b*
do
SUFFIX=`echo $i | cut -f2 -d'_'`
echo ${SUFFIX}
done
If you rename utility then just do:
rename -n 's/v5\4.b/v5.7/' v5.4b*
PS: -n is for dry-run. You may remove it later for real renaming.
If rename is not available then use:
for i in v5.4b*; do
echo mv "$i" "${i/v5.4b/v5.7}"
done
Remove 'echo` if you're satisfied with the output.
Related
I'm trying to write a shell script that deletes duplicate commands from my zsh_history file. Having no real shell script experience and given my C background I wrote this monstrosity that seems to work (only on Mac though), but takes a couple of lifetimes to end:
#!/bin/sh
history=./.zsh_history
currentLines=$(grep -c '^' $history)
wordToBeSearched=""
currentWord=""
contrastor=0
searchdex=""
echo "Currently handling a grand total of: $currentLines lines. Please stand by..."
while (( $currentLines - $contrastor > 0 ))
do
searchdex=1
wordToBeSearched=$(awk "NR==$currentLines - $contrastor" $history | cut -d ";" -f 2)
echo "$wordToBeSearched A BUSCAR"
while (( $currentLines - $contrastor - $searchdex > 0 ))
do
currentWord=$(awk "NR==$currentLines - $contrastor - $searchdex" $history | cut -d ";" -f 2)
echo $currentWord
if test "$currentWord" == "$wordToBeSearched"
then
sed -i .bak "$((currentLines - $contrastor - $searchdex)) d" $history
currentLines=$(grep -c '^' $history)
echo "Line deleted. New number of lines: $currentLines"
let "searchdex--"
fi
let "searchdex++"
done
let "contrastor++"
done
^THIS IS HORRIBLE CODE NOONE SHOULD USE^
I'm now looking for a less life-consuming approach using more shell-like conventions, mainly sed at this point. Thing is, zsh_history stores commands in a very specific way:
: 1652789298:0;man sed
Where the command itself is always preceded by ":0;".
I'd like to find a way to delete duplicate commands while keeping the last occurrence of each command intact and in order.
Currently I'm at a point where I have a functional line that will delete strange lines that find their way into the file (newlines and such):
#sed -i '/^:/!d' $history
But that's about it. Not really sure how get the expression to look for into a sed without falling back into everlasting whiles or how to delete the duplicates while keeping the last-occurring command.
The zsh option hist_ignore_all_dups should do what you want. Just add setopt hist_ignore_all_dups to your zshrc.
I wanted something similar, but I dont care about preserving the last one as you mentioned. This is just finding duplicates and removing them.
I used this command and then removed my .zsh_history and replacing it with the .zhistory that this command outputs
So from your home folder:
cat -n .zsh_history | sort -t ';' -uk2 | sort -nk1 | cut -f2- > .zhistory
This effectively will give you the file .zhistory containing the changed list, in my case it went from 9000 lines to 3000, you can check it with wc -l .zhistory to count the number of lines it has.
Please double check and make a backup of your zsh history before doing anything with it.
The sort command might be able to be modified to sort it by numerical value and somehow archieve what you want, but you will have to investigate further about that.
I found the script here, along with some commands to avoid saving duplicates in the future
I didn't want to rename the history file.
# dedupe_lines.zsh
if [ $# -eq 0 ]; then
echo "Error: No file specified" >&2
exit 1
fi
if [ ! -f $1 ]; then
echo "Error: File not found" >&2
exit 1
fi
sort $1 | uniq >temp.txt
mv temp.txt $1
Add dedupe_lines.zsh to your home directory, then make it executable.
chmod +x dedupe_lines.zsh
Run it.
./dedupe_lines.zsh .zsh_history
This question already has answers here:
Search+replace strings in filenames
(2 answers)
Closed 4 years ago.
I have many files like this
BBG-06-0645-01B.txt
BFDG_06-0219-01T.txt
MFD-02-0047-011T.txt
BBTF_06-0649-01N.txt
BFGD_02-2486-016J.txt
I am trying to replace any hyphens with underscores, like the following
BBG_06_0645_01B.txt
BFDG_06_0219_01T.txt
MFD_02_0047_011T.txt
BBTF_06_0649_01N.txt
BFGD_02_2486_016J.txt
I tried to use the following without sucecss
rename -nvs - _ *.txt
Using expression: sub { use feature ':5.18'; s/\Q${\"\-"}/_/ }
'BBG-06-0645-01B.txt' would be renamed to 'BBG_06-0645-01B.txt'
'BBTF_06-0649-01N.txt' would be renamed to 'BBTF_06_0649-01N.txt'
'BFDG_06-0219-01T.txt' would be renamed to 'BFDG_06_0219-01T.txt'
'BFGD_02-2486-016J.txt' would be renamed to 'BFGD_02_2486-016J.txt'
'MFD-02-0047-011T.txt' would be renamed to 'MFD_02-0047-011T.txt'
if I also use rename 's/\Q${\"\-"}/_/' *.txt it does not change - to _ for all of them
You could use rename like this:
rename "s/-/_/g" *.txt
dry run with the -n flag:
rename -n "s/-/_/g" *.txt
bash substitution with a for loop:
for file in *.txt; do mv "$file" "${file//-/_}" ; done
dry run with echo instead of mv:
for file in *.txt; do echo "$file" "${file//-/_}" ; done
Using pure BASH substitution here. Could you please try following.(For dry run only)
for file in *.txt
do
echo "mv $file ${file//-/_}"
done
This will only print the statements first, once you are happy with commands(mv commands) then remove echo from above command.
here is an example of output mv BBG-06-0645-01B.txt BBG_06_0645_01B.txt
Once you are happy with results above use following.
for file in *.txt
do
echo "File named $file is going to rename to ${file//-/_}"
mv "$file" "${file//-/_}"
done
In case you want to print status which file has been successfully renamed or not use:
for file in *.txt
do
echo "File named $file is going to rename to ${file//-/_}"
if mv "$file" "${file//-/_}"
then
echo "File named $file is successfully renamed to ${file//-/_}"
else
echo "Please check seems file $file did not rename."
fi
done
This question already has answers here:
Rename filename to another name
(3 answers)
Closed 7 years ago.
Let´s say I have a bunch of files named something like this: bsdsa120226.nai bdeqa140223.nai and I want to rename them to 120226.nai 140223.nai. How can i achieve this using the script below?
#!/bin/bash
name1=`ls *nai*`
names=`ls *nai*| grep -Po '(?<=.{5}).+'`
for i in $name1
do
for y in $names
do
mv $i $y
done
done
Solution:
name1=`ls *nai*`
for i in $name1
do
y=$(echo "$i" | grep -Po '(?<=.{5}).+')
mv $i $y
done
This:
#!/bin/bash
shopt -s extglob nullglob
for file in *+([[:digit:]]).nai; do
echo mv -nv -- "$file" "${file##+([^[:digit:]])}"
done
Remove the echo if you're happy with the mv commands.
Note. This solution does not assume that there are 5 leading characters to delete. It will delete all the leading non-numeric characters.
Using only bash, you could do this:
for file in *nai* ; do
echo mv -- "$file" "${file:5}"
done
(Remove the echo when satisfied with the output.)
Avoid ls in scripts, except for displaying information. Use plain globbing instead.
See also How do I do string manipulations in bash? for more string manipulation techniques.
Your script can't work with that structure: if you have 5 files, it will call mv five times for the first file (once for each element in the second list), five times for the second, etc. You'd need to iterate over the two sets of names in lockstep. (It also doesn't deal with things like whitespace in filenames.)
You would be better off using rename (prename on some systems) since that allows you to use Perl regular expressions to do the renaming, along the lines of:
prename 's/^.{5}//' *.nai
The reason your script is not behaving is that, for every source file, you're attempting to rename it to every target file.
If you need to limit yourself to using that script, you need to work out the single target file for each source file, something like:
#!/bin/bash
for i in *.nai; do
y=$(echo "$i" | cut -c6-)
mv "$i" "$y"
done
If your system has rename tool, it's better to go with the simple rename command,
rename 's/^.{5}//' *.nai
It just remove the first 5 characters from the file name.
OR
for i in *.nai; do mv "$i" $(grep -oP '(?<=^.{5}).+' <<< "$i"); done
This question already has answers here:
Bash input for multiple file
(5 answers)
Closed 8 years ago.
I have thousands of two set of files, one with name.ext and another set for the same name ending with name.new.psl. So for every name.ext there is a name.new.psl. Now I have to pass this as arguments to a script such as customise.pl name.ext name.new.psl
Any ideas for a loop in bash? The first name is common for each name.ext and name.new.psl like:
perl customise.pl name.ext name.new.psl
for f in *.ext ; do
perl customise.pl "${f}" "${f/%.txt/.new.psl}"
done
Will do it for you in the current working directory.
for fname in *.ext
do
perl customise.pl "$fname" "${fname%.ext}.new.psl"
done
The above does not require any special bash features. So, it is compatible with, for example, dash which is the default shell (/bin/sh) on debian-derived distributions.
The trick above is that ${fname%.ext} tells the shell to remove the text .ext from the end of $fname, leaving just the "name" part. Thus, "${fname%.ext}.new.psl" removes .ext adds the .new.psl extension.
The file names in the code above are in double-quotes. This is so that this script will work even if the file names have spaces in them.
for i in `ls *.ext`; do NAME=`echo $i | awk -F '.' '{print $1}'`; perl customise.pl $NAME.ext $NAME.new.psl; done
This question already has answers here:
Rename multiple files based on pattern in Unix
(24 answers)
Closed 5 years ago.
I have loads of files which look like this:
DET01-ABC-5_50-001.dat
...
DET01-ABC-5_50-0025.dat
and I want them to look like this:
DET01-XYZ-5_50-001.dat
...
DET01-XYZ-5_50-0025.dat
How can I do this?
There are a couple of variants of a rename command, in your case, it may be as simple as
rename ABC XYZ *.dat
You may have a version which takes a Perl regex;
rename 's/ABC/XYZ/' *.dat
for file in *.dat ; do mv $file ${file//ABC/XYZ} ; done
No rename or sed needed. Just bash parameter expansion.
Something like this will do it. The for loop may need to be modified depending on which filenames you wish to capture.
for fspec1 in DET01-ABC-5_50-*.dat ; do
fspec2=$(echo ${fspec1} | sed 's/-ABC-/-XYZ-/')
mv ${fspec1} ${fspec2}
done
You should always test these scripts on copies of your data, by the way, and in totally different directories.
You'll need to learn how to use sed http://unixhelp.ed.ac.uk/CGI/man-cgi?sed
And also to use for so you can loop through your file entries http://www.cyberciti.biz/faq/bash-for-loop/
Your command will look something like this, I don't have a term beside me so I can't check
for i in `dir` do mv $i `echo $i | sed '/orig/new/g'`
I like to do this with sed. In you case:
for x in DET01-*.dat; do
echo $x | sed -r 's/DET01-ABC-(.+)\.dat/mv -v "\0" "DET01-XYZ-\1.dat"/'
done | sh -e
It is best to omit the "sh -e" part first to see what will be executed.
All of these answers are simple and good. However, I always like to add an interactive mode to these scripts so that I can find false positives.
if [[ -n $inInteractiveMode ]]
then
echo -e -n "$oldFileName => $newFileName\nDo you want to do this change? [Y/n]: "
read run
[[ -z $run || "$run" == "y" || "$run" == "Y" ]] && mv "$oldFileName" "$newFileName"
fi
Or make interactive mode the default and add a force flag (-f | --force) for automated scripts or if you're feeling daring. And this doesn't slow you down too much: the default response is "yes, I do want to rename" so you can just hit the enter key at each prompt (because of the -z $run test.