15

Can someone suggest on how to rename the file name:

head.body.date.txt

To:

head_body_date.txt

Is there a single line statement to do the rename in Unix?

3
  • 11
    Do you assume that .tar.gz is an extension? Also, welcome to U&L!
    – EKons
    Oct 11, 2016 at 11:02
  • 2
    Do you really mean Unix as opposed to Linux? Which Unix?
    – terdon
    Oct 11, 2016 at 12:05
  • 1
    The obvious answer is mv head.body.date.txt head_body_date.txt. If you have other restrictions or real examples, then please update your question with these. As it stands now, it's unclear whether the question refers to a file with that explicit name, a single directory where all files have a particular format, or if you want to search a directory hierarchy for particular names and change these.
    – Kusalananda
    Jul 16, 2018 at 12:43

7 Answers 7

12

Iterate over the filenames, and use Parameter expansion for conversion:

for f in *.*.*.txt; do i="${f%.txt}"; echo mv -i -- "$f" "${i//./_}.txt"; done

The parameter expansion pattern, ${f//./_} replaces all .s with _s in the filename ($f).

The above will do a dry-run, to let the actual renaming take place, remove echo:

for f in *.*.*.txt; do i="${f%.txt}"; mv -i -- "$f" "${i//./_}.txt"; done

If you want to deal with any extension, not just .txt:

for f in *.*.*.*; do pre="${f%.*}"; suf="${f##*.}"; \
                     echo mv -i -- "$f" "${pre//./_}.${suf}"; done

After checking remove echo for actual action:

for f in *.*.*.*; do pre="${f%.*}"; suf="${f##*.}"; \
                     mv -i -- "$f" "${pre//./_}.${suf}"; done

Generic, for arbitrary number of dots, at least one:

for f in *.*; do pre="${f%.*}"; suf="${f##*.}"; \
                 mv -i -- "$f" "${pre//./_}.${suf}"; done
8
  • is there a general way if there is an arbitrary number of dots ? Oct 11, 2016 at 9:21
  • 1
    @CiprianTomoiaga Do: for f in *.*; do ...; done
    – heemayl
    Oct 11, 2016 at 9:26
  • I might suggest putting the *.* pattern straight in the example, since now the code looks like it works only with a fixed number of dots.
    – ilkkachu
    Oct 11, 2016 at 11:23
  • @ilkkachu Fair enough, edited.
    – heemayl
    Oct 11, 2016 at 11:26
  • Thank you so much heemay, it worked for me. and some learning!
    – S G
    Oct 11, 2016 at 16:51
5

with perl based rename

$ rename -n 's/\.[^.]+$(*SKIP)(*F)|\./_/g' head.body.date.txt 
rename(head.body.date.txt, head_body_date.txt)
  • \.[^.]+$(*SKIP)(*F) skip this pattern and look for alternate matches
  • |\./_/g replace all . with _

Or, using negative lookahead

$ rename -n 's/\.(?![^.]+$)/_/g' head.body.date.txt 
rename(head.body.date.txt, head_body_date.txt)

Once this is okay, remove the -n option

4
rename 's/\.(?=[^.]*\.)/_/g' *.txt

Uses regex replacement to find all but the last instance of . in the filename (non-capturing lookahead) and replace them with _. Generalises to *.* if you like.

(this particular version of rename seems to be installed via util-linux). I'm using Ubuntu 12.04 (yes, severely out of date machine).

2

bash:

old=head.body.date.txt oldb=${old%.*} olde=${old##*.} ; \
mv -- "$old" "${oldb//./_}.${olde}"
2
  • (1) You should use mv --, to guard against “old” filenames that begin with -. (2) You should put $old in quotes ("$old"), to guard against “old” filenames that contain spaces or characters that are special to the shell (like * and ?). (3) Your answer fails in the somewhat pathological case in which the extension contains an underscore: head.body.date.t_xt gets renamed to head_body_date_t.xt. Oct 12, 2016 at 3:10
  • @Scott, thanks. The code is now tweaked as per all three items mentioned.
    – agc
    Oct 12, 2016 at 4:02
1

Using mv, sed and rev in one line:

mv "head.body.date.txt" "$(echo head.body.date.txt | rev | sed 's/\./_/2g' | rev)"

If you want to apply it on all txt files in current rep, using globs seems tricky because of how mv works, but you can do a for loop in a one-liner:

for file in *.txt; do mv "$file" "$(echo $file | rev | sed 's/\./_/2g' | rev)"; done

Slightly longer, but you can match several patterns in your loop !

3
  • 2
    (1) Arrghhh! Don’t do $(ls …); just do for file in *.txt. (2) You should use more quotes: mv "$file" "$(…)". Oct 12, 2016 at 3:11
  • Thank you Scott, duly noted and edited ! I am still a youngster in shell, please be gentle ;) Oct 12, 2016 at 7:53
  • In hindsight, using rename like several others have suggested in their answers does sound simpler though... Oct 12, 2016 at 8:03
0

Came up with this one just yesterday. It's plain, simple, easy to understand.

eg Mary.had.a.little.lamb.mp3

name w/o extension
extension="${filename##.}"
#extension w/o name
filename="${filename%.
}"
changes . to _
newfilename="$(rename 's/./_/g' "$filename")"
newestfilename="$newfilename"."$extension"

Mary_had_a_little_lamb.mp3

Note: the # and anything after are for explanation only and you do not have to use them.

requires perl rename

To do an entire folder, wrap in a 'for' loop

for w in *; do the stuff above done

I wasted a couple of hours trying to make it recursive to no avail. What I've done is a) write the script to do one entire folder - do not try to make it recursive. Make it executable.

b) install 'worker' file manager from the repositories.

On the bottom of the screen is a purple stripe - click on it and see the bank of buttons change.

On the very top of the screen, extreme left you'll see an 'A'. to the right of the 'A' is a button with some sort of symbol on it. Left click brings up the settings menu.

Click 'button configuration' and you'll see a couple of banks of buttons you can scroll through the banks if you wish.

When you've chosen your button, click on it. A new window will open up. Enter what you want the button, like 'recursive . to _' without the quotes. Two buttons above the blue hi-lited 'Configure' button is one that says 'Add Command'. Click it.

Click 'Own Command'

Click 'Okay'

Click 'Recursive' - check marks on the buttons are kind of hard to see.

Start mode should be 'normal mode'

You can use find button or type in the path beside it.

Lastly, hit any 'Okay' 'Save' buttons on your way out. After that, you know the drill. click on one of the panels to select it, navigate to the folder you want to do magi in. You can figure it out from there.

NOTE" I have tried off and on for over 10 years to get ffmpeg to recurse, once I figured it out and wrapped my 'do-this-to-one-entire-folder script, It recursed the entire of branch of folder/subfolder/etc perfectly.

DISCLAIMER: I am only a worker user and have no other connection to it.

1
  • 1
    I'm sorry to say but this is more than a little unclear. What's that script looking thing in the beginning? How to run it? Why is perl rename required as it's not in the script? Doesn't the script do the job alone, how is worker related to all this? What "button" and "panels" are you talking about? Maybe edit your question, adding the full script (if that's what it is) complete with the { code } brackets, clarifying screenshots etc. Feb 21, 2023 at 14:02
-1
 mv head.body.date.txt head_body_date.txt

It is as simple as that for a single file at a time. You do no specify whether you would like this done recursively within a directory or to the whole directory at all, so that would be your most convenient, quick solution.

1
  • I don't get the minus votes here, this answer DOES exactly what OP is asking for within the constraints he exposed. The fact there are different and potentially more portable solutions does not mean this one is bad, the question called for it.
    – Attila
    Jun 19, 2023 at 12:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .