Upload Tizen:Base source
[external/bash.git] / examples / scripts.v2 / ren
1 #!/bin/bash
2 #@ This program came from: ftp://ftp.armory.com/pub/scripts/ren
3 #@ Look there for the latest version.
4 #@ If you don't find it, look through http://www.armory.com/~ftp/
5 #
6 # @(#) ren 2.1.1 2002-03-17
7 # 1990-06-01 John H. DuBois III (john@armory.com)
8 # 1991-02-25 Improved help info
9 # 1992-06-07 Remove quotes from around shell pattern as required by new ksh
10 # 1994-05-10 Exit if no globbing chars given.
11 # 1995-01-23 Allow filename set to be given on command line.
12 # 1997-09-24 1.4 Let [] be used for globbing.  Added x option.
13 # 1997-11-26 1.4.1 Notice if the sequences of globbing chars aren't the same.
14 # 1999-05-13 Changed name to ren to avoid conflict with /etc/rename
15 # 2000-01-01 1.4.2 Let input patterns that contain whitespace be used.
16 # 2001-02-14 1.5 Better test for whether old & new globbing seqs are identical.
17 # 2001-02-20 1.6 Added pP options.
18 # 2001-02-27 1.7 Added qf options.  Improved interpretation of rename patterns.
19 # 2001-05-10 1.8 Allow multiple pP options.  Added Qr options.
20 # 2001-07-25 2.0 Added mz options.
21 # 2001-11-25 2.1 Allow segment ranges to be given with -m.  Work under ksh93.
22 # 2002-03-17 2.1.1 Fixed bug in test for legal expressions.
23
24 # todo: It would be nice to be able to escape metacharacters with '\'
25 # todo: Should enhance patterns to make ] in a pair of brackets work ([]])
26 # todo: Allow use of all ksh globbing patterns.
27 # todo: Allow use of extended regexps, with () to enumerate pieces and \num to
28 # todo: select them.
29 #
30 # Modifications for bash made by Chet Ramey <chet@po.cwru.edu>
31
32 name=${0##*/}
33 Usage="Usage:
34 $name [-fhqtv] [-m<segstart[:segend]=operation>] [-z<len>] [-[pP]<pattern>] 
35     oldpattern [newpattern [filename ...]]
36 or
37 $name -r [same options as above] oldpattern newpattern directory ..."
38 tell=false
39 verbose=false
40 warn=true
41 warnNoFiles=true
42 debug=false
43 recurse=false
44 inclPat=
45 exclPat=
46 declare -i inclCt=0 exclCt=0
47 check=true
48 declare -i j op_end_seg
49
50 # Begin bash additions
51 shopt -s extglob
52
53 #
54 # ksh print emulation
55 #
56 #       print [-Rnprsu[n]] [-f format] [arg ...]
57 #
58 #       -       end of options
59 #       -R      BSD-style -- only accept -n, no escapes
60 #       -n      do not add trailing newline
61 #       -p      no-op (no coprocesses)
62 #       -r      no escapes
63 #       -s      print to the history file
64 #       -u n    redirect output to fd n
65 #       -f format       printf "$format" "$@"
66 #
67
68 print()
69 {
70         local eflag=-e
71         local nflag= fflag= c
72         local fd=1
73
74         OPTIND=1
75         while getopts "fRnprsu:" c
76         do
77                 case $c in
78                 R)      eflag= ;;
79                 r)      eflag= ;;
80                 n)      nflag=-n ;;
81                 s)      sflag=y ;;
82                 f)      fflag=y ;;
83                 u)      fd=$OPTARG ;;
84                 p)      ;;
85                 esac
86         done
87         shift $(( $OPTIND - 1 ))
88
89         if [ -n "$fflag" ]; then
90                 builtin printf "$@" >&$fd
91                 return
92         fi
93
94         case "$sflag" in
95         y)      builtin history -s "$*" ;;
96         *)      builtin echo $eflag $nflag "$@" >&$fd
97         esac
98 }
99
100 # End bash additions
101
102 while getopts :htvxp:P:fqQrm:z: opt; do
103     case $opt in
104     h)
105         print -r -- \
106 "$name: rename files by changing parts of filenames that match a pattern.
107 $Usage
108 oldpattern and newpattern are subsets of sh filename patterns; the only
109 globbing operators (wildcards) allowed are ?, *, and [].  All filenames that
110 match oldpattern will be renamed with the filename characters that match the
111 constant (non-globbing) characters of oldpattern changed to the corresponding
112 constant characters of newpattern.  The characters of the filename that match
113 the globbing operators of oldpattern will be preserved.  Globbing operators
114 in oldpattern must occur in the same order in newpattern; for every globbing
115 operators in newpattern there must be an identical globbing operators in
116 oldpattern in the same sequence.  Both arguments should be quoted since
117 globbing operators are special to the shell.  If filenames are given, only
118 those named are acted on; if not, all filenames that match oldpattern are acted
119 on.  newpattern is required in all cases except when -m is given and no further
120 arguments are given.
121 If you are unsure whether a $name command will do what you intend, issue it
122 with the -t option first to be sure.
123 Examples:
124 $name \"/tmp/foo*.ba.?\" \"/tmp/new*x?\"
125     All filenames in /tmp that match foo*.ba.? will have the \"foo\" part
126     replaced by \"new\" and the \".ba.\" part replaced by \"x\".
127     For example, /tmp/fooblah.ba.baz would be renamed to /tmp/newblahxbaz.
128 $name \* \*- foo bar baz
129     foo, bar, and baz will be renamed to foo-, bar-, and baz-.
130 $name '????????' '????-??-??'
131     All filenames that are 8 characters long will be changed such that dashes
132     are inserted after the 4th and 6th characters.
133 Options:
134 -h: Print this help.
135 -r: Recursive operation.  Filenames given on the command line after oldpattern
136     and newpattern are taken to be directories to traverse recursively.  For
137     each subdirectory found, the specified renaming is applied to any matching
138     filenames.  oldpattern and newpattern should not include any directory
139     components.
140 -p<pattern>, -P<pattern>: Act only on filenames that do (if -p is given) or do
141     not (if -P is given) match the sh-style filename globbing pattern
142     <pattern>.  This further restricts the filenames that are acted on, beyond
143     the filename selection produced by oldpattern and the filename list (if
144     any).  <pattern> must be quoted to prevent it from being interpreted by the
145     shell.  Multiple instances of these options may be given.  In this case,
146     filenames are acted on only if they match at least one of the patterns
147     given with -p and do not match any of the patterns given with -P.
148 -m<segstart[:segend]=operation>: For each file being renamed, perform a
149     mathematical operation on the string that results from concatenating
150     together the filename segments that matched globbing operator numbers
151     segstart through segend, where operators are numbered in order of
152     occurrence from the left.  For example, in the pattern a?b*c[0-9]f, segment
153     1 consists of the character that matched ?, segment 2 consists of the
154     character(s) that matched *, and segment 3 consists of the character that
155     matched [0-9].  The selected segments are replaced with the result of the
156     mathematical operation.
157     The concatenated string must consist of characters that can be interpreted
158     as a decimal integer; if it does not, the filename is not acted on.  This
159     number is assigned to the variable 'i', which can be referenced by the
160     operation.  The operations available are those understood by the ksh
161     interpreter, which includes most of the operators and syntax of the C
162     language.  The original filename segment is replaced by the result of the
163     operation.  If -m is used, newpattern may be an empty string or not given
164     at all (if no directory/file names are given).  In this case, it is taken
165     to be the same as oldpattern.
166     If segend is given, any fixed text that occurs in the pattern between the
167     starting and ending globbing segments is discarded.  If there are fewer
168     globbing segments than segend, no complaint is issued; the string is formed
169     from segment segstart through the last segment that does exist.
170     If segend is not given, the only segment acted on is startseg.
171     Examples:
172     $name -m3=i+6 '??*.ppm'
173         This is equivalent to:
174         $name -m3=i+6 '??*.ppm' '??*.ppm'
175         Since the old pattern and new pattern are identical, this would
176         normally be a no-op.  But in this case, if a filename of ab079.ppm is
177         given, it is changed to ab85.ppm.
178     $name '-m1:2=i*2' 'foo??bar'
179         This will change a file named foo12bar to foo24bar
180     $name '-m1:2=i*2' 'foo?xyz?bar'
181         This will also change a file named foo1xyz2bar to foo24bar
182 -z<len>: Set the size of the number fields that result when -m is used.  The
183     field is truncated to the trailing <len> digits or filled out to <len>
184     digits with leading zeroes.  In the above example, if -z3 is given, the
185     output filename will be ab085.ppm.  
186 -f: Force rename.  By default, $name will not rename files if a file with the
187     new filename already exists.  If -f is given, $name will carry out the
188     rename anyway.
189 -q: Quiet operation.  By default, if -f is given, $name will still notify the
190     user if a rename results in replacement of an already-existing filename. 
191     If -q is given, no notification is issued.
192 -Q: Suppress other warnings.  By default, a warning is issued if no files are
193     selected for acting upon.  If -Q is given, no warning is issued.
194 -v: Show the rename commands being executed.
195 -t: Show what rename commands would be done, but do not carry them out."
196         exit 0
197         ;;
198     f)
199         check=false
200         ;;
201     q)
202         warn=false
203         ;;
204     Q)
205         warnNoFiles=false
206         ;;
207     r)
208         warnNoFiles=false
209         recurse=true
210         ;;
211     t)
212         tell=true
213         ;;
214     v)
215         verbose=true
216         ;;
217     x)
218         verbose=true
219         debug=true
220         ;;
221     p)
222         inclPats[inclCt]=$OPTARG
223         ((inclCt+=1))
224         ;;
225     P)
226         exclPats[exclCt]=$OPTARG
227         ((exclCt+=1))
228         ;;
229     m)
230         # Store operation for each segment number in ops[num]
231         # Store ending segment number in op_end_seg[num]
232         range=${OPTARG%%=*}
233         op=${OPTARG#*=}
234         start=${range%%:*}
235         end=${range#*:}
236         if [[ "$start" != +([0-9]) || "$start" -eq 0 ]]; then
237             print -ru2 -- "$name: Bad starting segment number given with -m: $start"
238             exit 1
239         fi
240         if [[ "$end" != +([0-9]) || "$end" -eq 0 ]]; then
241             print -ru2 -- "$name: Bad ending segment number given with -m: $end"
242             exit 1
243         fi
244         if [[ start -gt end ]]; then
245             print -ru2 -- "$name: Ending segment ($end) is less than starting segment ($start)"
246             exit 1
247         fi
248         if [[ "$op" != @(|*[!_a-zA-Z0-9])i@(|[!_a-zA-Z0-9]*) ]]; then
249             print -ru2 -- \
250             "$name: Operation given with -m does not reference 'i': $op"
251             exit 1
252         fi
253         # Test whether operation is legal.  let returns 1 both for error
254         # indication and when last expression evaluates to 0, so evaluate 1
255         # after test expression.
256         i=1
257         let "$op" 1 2>/dev/null || {
258             print -ru2 -- \
259             "$name: Bad operation given with -m: $op"
260             exit 1
261         }
262         ops[start]=$op
263         op_end_seg[start]=$end
264         ;;
265     z)
266         if [[ "$OPTARG" != +([0-9]) || "$OPTARG" -eq 0 ]]; then
267             print -ru2 -- "$name: Bad length given with -z: $OPTARG"
268             exit 1
269         fi
270         typeset -Z$OPTARG j || exit 1
271         ;;
272     +?) # no way to tell getopts to not treat +x as an option
273         print -r -u2 "$name: Do not prefix options with '+'."
274         exit 1
275         ;;
276     :) 
277         print -r -u2 \
278 "$name: Option -$OPTARG requires a value.
279 $Usage
280 Use -h for help."
281         exit 1
282         ;;
283     \?) 
284         print -r -u2 \
285 "$name: -$OPTARG: no such option.
286 $Usage
287 Use -h for help."
288         exit 1
289         ;;
290     esac
291 done
292  
293 # remove args that were options
294 let OPTIND=OPTIND-1
295 shift $OPTIND
296
297 oldpat=$1
298 newpat=$2
299
300 # If -m is given, a non-existant or null newpat should be set to oldpat
301 if [ ${#ops[*]} -gt 0 ]; then
302     case $# in
303     0)
304         ;;
305     1)
306         set -- "$oldpat" "$oldpat"
307         newpat=$oldpat
308         $debug && print -ru2 -- "Set new pattern to: $newpat"
309         ;;
310     *)
311         if [ -z "$newpat" ]; then
312             shift 2
313             set -- "$oldpat" "$oldpat" "$@"
314             newpat=$oldpat
315             $debug && print -ru2 -- "Set new pattern to: $newpat"
316         fi
317         ;;
318     esac
319 fi
320
321 # Make sure input patterns that contain whitespace can be expanded properly
322 IFS=
323
324 origPat=$oldpat
325
326 # Generate list of filenames to act on.
327 case $# in
328 [01])
329     print -u2 "$Usage\nUse -h for help."
330     exit 1
331     ;;
332 2)
333     if $recurse; then
334         print -r -u2 "$name: No directory names given with -r.  Use -h for help."
335         exit 1
336     fi
337     set -- $oldpat      # Get list of all filenames that match 1st globbing pattern.
338     if [[ ! -a $1 ]]; then
339         $warnNoFiles && print -r -- "$name: No filenames match this pattern: $oldpat"
340         exit
341     fi
342     ;;
343 *)
344     shift 2
345     ;;
346 esac
347
348 integer patSegNum=1 numPatSegs
349
350 # For old ksh
351 # while [[ "$oldpat" = *'[\*\?]'* ]]; do
352
353 # Example oldpat: foo*.a
354 # Example newpat: bar*.b
355
356 # Build list of non-pattern segments and globbing segments found in arguments.
357 # Note the patterns given are used to get the list of filenames to act on,
358 # to delimit constant segments, and to determine which parts of filenames are
359 # to be replaced.
360 # Examples given for first iteration (in the example, the only iteration)
361 # The || newpat  is to ensure that new pattern does not have more globbing
362 # segments than old pattern
363 while [[ "$oldpat" = *@([\*\?]|\[+([!\]])\])* ||
364          "$newpat" = *@([\*\?]|\[+([!\]])\])* ]]; do
365     ## Get leftmost globbing pattern in oldpat
366
367     # Make r be oldpat with smallest left piece that includes a globbing
368     # pattern removed from it
369     r=${oldpat#*@([\*\?]|\[+([!\]])\])} # r=.a
370     # Make pat be oldpat with the above removed from it, leaving smallest
371     # left piece that includes a globbing pattern
372     pat=${oldpat%%"$r"}                 # pat=foo*
373     # Make l be pat with the globbing pattern removed from the right,
374     # leaving a constant string
375     l=${pat%@([\*\?]|\[+([!\]])\])}     # l=foo
376     # Remove the constant part of pat from the left, leaving the globbing
377     # pattern
378     pat=${pat#"$l"}                     # pat=*
379
380     # Do the same thing for newpat, solely to provide a reliable test that
381     # both oldpat & newpat contain exactly the same sequence of globbing
382     # patterns.
383     r=${newpat#*@([\*\?]|\[+([!\]])\])} # r=.b
384     npat=${newpat%%"$r"}                # pat=bar*
385     l=${npat%@([\*\?]|\[+([!\]])\])}    # l=bar
386     npat=${npat#"$l"}                   # npat=*
387
388     if [[ "$pat" != "$npat" ]]; then
389         print -ru2 -- \
390 "$name: Old-pattern and new-pattern do not have the same sequence of globbing chars.
391 Pattern segment $patSegNum: Old pattern: $pat  New pattern: $npat"
392         exit 1
393     fi
394
395     ## Find parts before & after pattern
396     # oldpre[] stores the old constant part before the pattern,
397     # so that it can be removed and replaced with the new constant part.
398     oldpre[patSegNum]=${oldpat%%"$pat"*}        # oldpre[1]=foo
399     # oldsuf stores the part that follows the globbing pattern,
400     # so that it too can be removed.
401     # After oldpre[] & oldsuf[] have been removed from a filename, what remains
402     # is the part matched by the globbing pattern, which is to be retained.
403     oldsuf[patSegNum]=${oldpat#*"$pat"}         # oldsuf[1]=.a
404     # newpre[] stores the new constant part before the pattern,
405     # so that it can be used to replace the old constant part.
406     newpre[patSegNum]=${newpat%%"$pat"*}        # newpre[1]=bar
407     # Get rid of processed part of patterns
408     oldpat=${oldpat#${oldpre[patSegNum]}"$pat"} # oldpat=.a
409     newpat=${newpat#${newpre[patSegNum]}"$pat"} # newpat=.b
410     # Store either * or ? in pats[], depending on whether this segment matches 1
411     # or any number of characters.
412     [[ "$pat" = \[* ]] && pat=?
413     pats[patSegNum]=$pat
414     ((patSegNum+=1))
415 done
416
417 if [ patSegNum -eq 1 ]; then
418     print -u2 "No globbing chars in pattern."
419     exit 1
420 fi
421
422 oldpre[patSegNum]=${oldpat%%"$pat"*}    # oldpre[2]=.a
423 oldsuf[patSegNum]=${oldpat#*"$pat"}     # oldsuf[2]=.a
424 newpre[patSegNum]=${newpat%%"$pat"*}    # newpre[2]=.b
425
426 numPatSegs=patSegNum
427
428 if $debug; then
429     patSegNum=1
430     while [[ patSegNum -le numPatSegs ]]; do
431         print -ru2 -- \
432 "Old prefix: <${oldpre[patSegNum]}>   Old suffix: <${oldsuf[patSegNum]}>   New prefix: <${newpre[patSegNum]}>   Pattern: <${pats[patSegNum]}>"
433         ((patSegNum+=1))
434     done
435 fi
436
437 # Example filename: foox.a
438 # Example oldpat: foo*.a
439 # Example newpat: bar*.b
440
441 integer numFiles=0
442
443 # Usage: renameFile filename [dirname]
444 # [dirname] is a directory name to prefix filenames with when they are printed
445 # for informational purposes.
446 # Uses globals:
447 #     inclCt exclCt inclPats[] exclPats[] ops[]
448 #     numPatSegs oldpre[] oldsuf[] newpre[] pats[]
449 #     check warn tell verbose name
450 # Modifies globals: numFiles
451 function renameFile {
452     typeset file=$1 subdir=$2
453     integer patSegNum patnum
454     typeset origname porigname newfile matchtext pnewfile matchsegs
455     integer startseg endseg
456
457     origname=$file      # origname=foox.a
458     porigname=$subdir$file
459     # Unfortunately, ksh88 does not do a good job of allowing for patterns
460     # stored in variables.  Without the conditional expression being eval'ed,
461     # only sh patterns are recognized.  If the expression is eval'ed, full
462     # ksh expressions can be used, but then expressions that contain whitespace
463     # break unless the user passed a pattern with the whitespace properly
464     # quoted, which is not intuititive.  This is fixed in ksh93; full patterns
465     # work without being eval'ed.
466     if [ inclCt -gt 0 ]; then
467         patnum=0
468         while [ patnum -lt inclCt ]; do
469             [[ "$file" = ${inclPats[patnum]} ]] && break
470             ((patnum+=1))
471         done
472         if [ patnum -eq inclCt ]; then
473             $debug && print -ru2 -- "Skipping not-included filename '$porigname'"
474             return 1
475         fi
476     fi
477     patnum=0
478     while [ patnum -lt exclCt ]; do
479         if [[ "$file" = ${exclPats[patnum]} ]]; then
480             $debug && print -ru2 -- "Skipping excluded filename '$porigname'"
481             return 1
482         fi
483         ((patnum+=1))
484     done
485     # Extract matching segments from filename
486     ((numFiles+=1))
487     patSegNum=1
488     while [[ patSegNum -le numPatSegs ]]; do
489         # Remove a fixed prefix         iteration:      1               2
490         file=${file#${oldpre[patSegNum]}}                       # file=x.a      file=
491         # Save the part of this suffix that is to be retained.  To do this, we
492         # need to know what part of the suffix matched the current globbing
493         # segment.  If the globbing segment is a *, this is done by removing
494         # the minimum part of the suffix that matches oldsuf (since * matches
495         # the longest segment possible).  If the globbing segment is ? or []
496         # (the latter has already been coverted to ?), it is done by taking the
497         # next character.
498         if [ "${pats[patSegNum]}" == \? ]; then
499             matchtext=${file#?}
500             matchtext=${file%$matchtext}
501         else
502             matchtext=${file%${oldsuf[patSegNum]}}              # matchtext=x   matchtext=
503         fi
504         $debug && print -ru2 -- "Matching segment $patSegNum: $matchtext"
505         file=${file#$matchtext}                         # file=.a       file=.a
506
507         matchsegs[patSegNum]=$matchtext
508         ((patSegNum+=1))
509     done
510
511     # Paste fixed and matching segments together to form new filename.
512     patSegNum=0
513     newfile=
514     while [[ patSegNum -le numPatSegs ]]; do
515         matchtext=${matchsegs[patSegNum]}
516         startseg=patSegNum
517         if [ -n "${ops[startseg]}" ]; then
518             endseg=${op_end_seg[startseg]}
519             while [ patSegNum -lt endseg ]; do
520                 ((patSegNum+=1))
521                 matchtext=$matchtext${matchsegs[patSegNum]}
522             done
523             if [[ "$matchtext" != +([-0-9]) ]]; then
524                 print -ru2 -- \
525 "Segment(s) $startseg - $endseg ($matchtext) of file '$porigname' do not form an integer; skipping this file."
526                 return 2
527             fi
528             i=$matchtext
529             let "j=${ops[startseg]}" || {
530                 print -ru2 -- \
531 "Operation failed on segment(s) $startseg - $endseg ($matchtext) of file '$file'; skipping this file."
532                 return 2
533             }
534             $debug && print -ru2 -- "Converted $matchtext to $j"
535             matchtext=$j
536         fi
537         newfile=$newfile${newpre[startseg]}$matchtext           # newfile=barx  newfile=barx.b
538         ((patSegNum+=1))
539     done
540
541     pnewfile=$subdir$newfile
542     if $check && [ -e "$newfile" ]; then
543         $warn &&
544         print -ru2 -- "$name: Not renaming \"$porigname\"; destination filename \"$pnewfile\" already exists."
545         return 2
546     fi
547     if $tell; then
548         print -n -r -- "Would move: $porigname -> $pnewfile"
549         $warn && [ -e "$newfile" ] && print -n -r " (destination filename already exists; would replace it)"
550         print ""
551     else
552         if $verbose; then
553             print -n -r -- "Moving: $porigname -> $pnewfile"
554             $warn && [ -e "$newfile" ] && print -n -r -- " (replacing old destination filename \"$pnewfile\")"
555             print ""
556         elif $warn && [ -e "$newfile" ]; then
557             print -r -- "$name: Note: Replacing old file \"$pnewfile\""
558         fi
559         mv -f -- "$origname" "$newfile"
560     fi
561 }
562
563 if $recurse; then
564     oPWD=$PWD
565     find "$@" -depth -type d ! -name '*
566 *' -print | while read dir; do
567         cd -- "$oPWD"
568         if cd -- "$dir"; then
569             for file in $origPat; do
570                 renameFile "$file" "$dir/"
571             done
572         else
573             print -ru2 -- "$name: Could not access directory '$dir' - skipped."
574         fi
575     done
576 else
577     for file; do
578         renameFile "$file"
579     done
580 fi
581
582 if [ numFiles -eq 0 ]; then
583     $warnNoFiles && print -ru2 -- \
584     "$name: All filenames were excluded by patterns given with -p or -P."
585 fi