9

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?

  • 9
    Do you assume that .tar.gz is an extension? Also, welcome to U&L! – EKons Oct 11 '16 at 11:02
  • 2
    Do you really mean Unix as opposed to Linux? Which Unix? – terdon Oct 11 '16 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 '18 at 12:43
9

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
  • is there a general way if there is an arbitrary number of dots ? – Ciprian Tomoiagă Oct 11 '16 at 9:21
  • 1
    @CiprianTomoiaga Do: for f in *.*; do ...; done – heemayl Oct 11 '16 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 '16 at 11:23
  • @ilkkachu Fair enough, edited. – heemayl Oct 11 '16 at 11:26
  • Thank you so much heemay, it worked for me. and some learning! – S G Oct 11 '16 at 16:51
4

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

3
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}"
  • (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. – Scott Oct 12 '16 at 3:10
  • @Scott, thanks. The code is now tweaked as per all three items mentioned. – agc Oct 12 '16 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 !

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.