JREN.BAT v2.8 - Rename files/folders using regular expressions

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: JREN.BAT - Rename files/folders using regular expression

#16 Post by dbenham » 15 Jul 2015 15:46

I recently made some changes to the HELP and called it Version 2.4.
I developed version 2.3 back in March for improved /T and /LIST options, but neglected to post it.

The changes since 2.2 are fairly minor:

1) If using /T (test mode), then illegal characters in resultant file name causes abort with an error message.
2) If using /LIST option, then illegal characters are allowed (ignored) in the output.
3) Added /?? and /??TS() options for paged help via MORE

JREN.BAT Version 2.4

Code: Select all

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
@goto :Batch

::JREN.BAT version 2.4 by Dave Benham
::
::  Release History:
::    2.4  2015-07-15: Added /?? and /??TS() paged help options
::    2.3  2015-03-03: Ignore invalid characters if /LIST mode
::                     Bug fix - abort if invalid character with /T mode
::    2.2  2014-12-10: Bug fix - forgot to make the search regex global
::    2.1  2014-12-10: Additional dt: options for FileSystemObject timestamps
::    2.0  2014-12-07: New /LIST option.
::                     Added ts() function and /?ts() documentation
::                     Many new JScript functions for use with /J with /LIST
::                     Added GOTO at top to improve startup performance
::    1.1  2014-12-02: Options may be prefaced with / or -
::                     Corrected some documentation
::    1.0  2014-11-30: Initial release
::
::============ Documentation ===========
:::
:::JREN  Search  Replace  [/Option  [Value]]...
:::JREN  /?[REGEX|REPLACE|VERSION|TS()]
:::JREN  /??[ts()]
:::
:::  Rename files in the current directory by performing a regular expression
:::  search/replace on the old file name to generate the new file name.
:::  This includes read only, hidden, and system files.
:::
:::  Search  - By default, this is a case sensitive JScript (ECMA) regular
:::            expression expressed as a string. The search is applied globally
:::            to the entire file name.
:::
:::            JScript regex syntax documentation is available at
:::            http://msdn.microsoft.com/en-us/library/ae5bf541(v=vs.80).aspx
:::
:::  Replace - By default, this is the string to be used as a replacement for
:::            each found search expression. Full support is provided for
:::            substituion patterns available to the JScript replace method.
:::
:::            For example, $& represents the portion of the source that matched
:::            the entire search pattern, $1 represents the first captured
:::            submatch, $2 the second captured submatch, etc. A $ literal
:::            can be escaped as $$.
:::
:::            An empty replacement string must be represented as "".
:::
:::            Replace substitution pattern syntax is fully documented at
:::            http://msdn.microsoft.com/en-US/library/efy6s3e6(v=vs.80).aspx
:::
:::  Options:  Behavior may be altered by appending one or more options.
:::  The option names are case insensitive, and may appear in any order
:::  after the Replace argument. Options may be prefaced with / or -
:::
:::      /D  - Rename Directories instead of files.
:::
:::      /FM FileOrFolderMask
:::
:::            Only rename files or folders that match any of the pattern(s)
:::            using standard wildcards. Multiple patterns are delimited by a
:::            pipe (|). Only complete name matches count.
:::
:::              * matches any 0 or more characters
:::              ? matches any 0 or 1 character except .
:::
:::      /FX FileOrFolderExclusion
:::
:::            Exclude files or folders that match any of the pattern(s)
:::            using standard wildcards. Multiple patterns are delimited by a
:::            pipe (|). Only complete name matches count.
:::
:::              * matches any 0 or more characters
:::              ? matches any 0 or 1 character except .
:::
:::      /I  - Ignore case when matching.
:::
:::      /J  - Treat Replace as a JScript expression.
:::            The following variables contain details about each match:
:::
:::              $0 = the substring that matched the Search
:::              $1 through $n = captured submatch strings
:::              $off = the offset where the match occurred
:::              $src = the original source string
:::
:::            The following are also available:
:::
:::              $n = An incrementing number for use in the name. The value
:::                   is reset to the /NBEG value for each directory.
:::                   It increases by the /NINC value for each renamed file.
:::                   The value may be zero padded to the width specified by
:::                   the /NPAD value.
:::
:::              lc(str)
:::
:::                 Convert str to lower case. Shorthand for str.toLowerCase().
:::
:::              uc(str)
:::
:::                 Convert str to upper case. Shorthand for str.toUpperCase().
:::
:::              lpad(string,pad)
:::
:::                 Used to left pad string str to a minimum length. If the
:::                 str already has length >= the pad string length, then no
:::                 change is made. Otherwise it left pads the value with the
:::                 characters of the pad string to the length of pad.
:::
:::                 Examples:
:::                    lpad(15,'0000')    returns "0015"
:::                    lpad(15,'    ')    returns "  15"
:::                    lpad(19011,'0000') returns "19011"
:::
:::              rpad(string,pad)
:::
:::                 Used to right pad the string to a minimum length. If the
:::                 str already has length >= the pad string length, then no
:::                 change is made. Otherwise it right pads the value with the
:::                 characters of the pad string to the length of pad.
:::
:::              ts( {option:value, option:value...} )
:::
:::                 Perform date/time computations and produce formatted
:::                 timestamps. This function can get the current date/and time,
:::                 or get the created/lastModified/lastAccessed timestamps for
:::                 the file being renamed, or parse a date/time from the name
:::                 of the file, or use a user specified value.
:::
:::                 Use JREN /?TS() to get help on the ts() function
:::
:::              attr( [offChar] )
:::
:::                 Lists the attributes of the file/folder. Set attributes are
:::                 listed in upper case and unset attributes are shown as the
:::                 offChar, or lower case. The listed attributes are:
:::                   R - Read Only
:::                   H - Hidden
:::                   S - System
:::                   A - Archive
:::                   L - Link or Shortcut
:::                   C - Compressed
:::
:::              size( [pad] )
:::
:::                 File/folder size, optionally left padded to the length of
:::                 the pad string.
:::
:::              type( [pad] )
:::
:::                 File/folder type, optionally right padded to the length of
:::                 the pad string.
:::
:::              name( [pad] )
:::
:::                 Name of the file/folder, optionally right padded to
:::                 the length of the pad string.
:::
:::              path( [pad] )
:::
:::                 Full Path of file/folder, optionally right padded to
:::                 the length of the pad string.
:::
:::              parent( [pad] )
:::
:::                 Path of the parent folder, optionally right padded to
:::                 the length of the pad string.
:::
:::              sName( [pad ])
:::
:::                 Short (8.3) name of the file/folder, optionally right padded
:::                 to the length of the pad string.
:::
:::              sPath( [pad] )
:::
:::                 Path of the file/folder using short (8.3) names, optionally
:::                 right padded to the length of the pad string.
:::
:::              sParent( [pad] )
:::
:::                 Path of the parent folder using short (8.3) names,
:::                 optionally right padded to the length of the pad string.
:::
:::      /L  - Convert names to Lower case. Entire names can be converted to
:::            lower case without any other changes by using empty strings ("")
:::            for both Search and Replace.
:::
:::      /LIST - List the Rename results only, without quotes, and without
:::            renaming anything. Invalid file name characters are ignored.
:::            The resulting "name" is always listed, even if no change.
:::
:::      /NBEG BeginValue
:::
:::            Specifies the initial $n value for each directory. The value
:::            must be an integer >= 0. The default value is 1.
:::
:::      /NINC IncrementValue
:::
:::            Specifies the amount $n is incremented after each rename.
:::            The value must be an integer >=1. The default value is 1.
:::
:::      /NPAD MinWidth
:::
:::            Specifies the minimum width for each $n value. If the $n value
:::            has fewer digits than MinWidth, then the value is zero padded
:::            on the left to achieve the MinWidth. The value must be >= 1.
:::            The default value is 3.
:::
:::      /P RootPath
:::
:::            Specifies the path where the rename is to take place.
:::            The default of . represents the current directory.
:::            Wildcards are not allowed.
:::
:::      /PM PathMask
:::
:::            Only rename files or folders whose parent folder path matches
:::            any of the PathMask pattern(s) using augmented wildcards.
:::            Multiple patterns are delimited by a pipe (|). Only full path
:::            matches count.
:::
:::              /P:  matches the root path specified by option /P
:::              **   matches any 0 or more characters
:::              *    matches any 0 or more characters except \
:::              ?    matches any 0 or 1 character except . or \
:::
:::           This option is only useful if the /S option is used.
:::
:::      /PX PathExclusion
:::
:::            Exclude files or folders whose parent folder path matches any
:::            of the PathExclusion pattern(s) using augmented wildcards.
:::            Multiple patterns are delimited by a pipe (|). Only full path
:::            matches count.
:::
:::              /P:  matches the root path specified by option /P
:::              **   matches any 0 or more characters
:::              *    matches any 0 or more characters except \
:::              ?    matches any 0 or 1 character except . or \
:::
:::           This option is only useful if the /S option is used.
:::
:::      /Q  - Do not list the renamed files/folders (Quiet mode).
:::
:::      /RFM RegexFileOrFoldereMask
:::
:::            Only rename files or folders that match the regular expression,
:::            ignoring case. Partial name matches count.
:::
:::      /RFX RegexFileOrFolderExclusion
:::
:::            Exclude files or folders that match the regular Expression,
:::            ignoring case. Partial name matches count.
:::
:::      /RPM RegexPathMask
:::
:::            Only rename files or folders whose parent folder path matches the
:::            RegexPathMask regular expression, ignoring case. Partial path
:::            matches count. This option is really only useful if the /S
:::            option is used.
:::
:::      /RPX RegexPathExclusion
:::
:::            Exclude files or folders whose parent folder path matches the
:::            RegexPathExclusion regular expression, ignoring case. Partial
:::            path matches count. This option is really only useful if the
:::            /S option is used.
:::
:::      /S  - Recurse Subdirectories.
:::
:::      /T  - List the rename operations that would be attempted,
:::            but do not rename anything. (Test mode)
:::
:::      /U  - Convert names to Upper case. Entire names can be converted to
:::            upper case without any other changes by using empty strings ("")
:::            for both Search and Replace.
:::
:::  Help is available by supplying a single argument beginning with /?:
:::
:::      /?        - Writes this help documentation to stdout.
:::      /??       - Same as /? except uses MORE to provide pagination
:::
:::      /?REGEX   - Opens up Microsoft's JScript regular expression
:::                  documentation within your browser.
:::
:::      /?REPLACE - Opens up Microsoft's JScript REPLACE documentation
:::                  within your browser.
:::
:::      /?VERSION - Writes the JREN version number to stdout.
:::
:::      /?TS()    - Writes documentation for the ts() function to stdout.
:::      /??TS()   - Same as /??TS() except uses MORE for pagination.
:::
:::  JREN.BAT was written by Dave Benham, and originally posted at
:::  http://www.dostips.com/forum/viewtopic.php?f=3&t=6081
:::
:: =============== ts() documentation ===============
::+
::+ts( [ { [option:value [,option:value]...] } ] )
::+ 
::+  A JScript function that can performs date and time computations and return
::+  a formatted time string.
::+ 
::+  The option object argument within curly braces is optional - if no argument
::+  is given, then it returns the current timestamp using compressed ISO 8601
::+  format with milliseconds and local time zone - YYYYMMDDThhmmss.fff+zzzz
::+ 
::+    Examples:
::+      ts()  - Current date/time:  YYYYMMDDThhmmss.fff+zzzz
::+      ts({dt:'created',fmt:'{iso-dt}'})  - The file create date:  YYYY-MM-DD
::+      ts({od:-1,fmt:'{YYYY}_{MM}_{DD}'}) - Yesterday's date:  YYYY_MM_DD
::+ 
::+  Option names are case sensitive. There are 5 types of options:
::+    1) Specify the base date          - dt:
::+    2) Specify date/time offsets      - oy: om: od: oh: on: os: of:
::+    3) Specify the output time zone   - tz:
::+    4) Specify the output format      - fmt:
::+    5) Configure the day-of-week and  - wkd: weekday: mth: month:
::+       month names for non-English
::+       users
::+
::+  Specify the base date and time
::+
::+    dt:  Value specifies the base date and time. Many formats supported:
::+
::+      Current local date/time
::+        - do not specify a dt: value
::+        - undefined value
::+        - empty string ''
::+
::+        Examples:
::+          dt:''
::+          dt:undefined
::+
::+      Milliseconds since 1970-01-01 00:00:00 UTC
::+        - NumericExpression (math OK)
::+        - NumericString (no math)
::+
::+        Examples:
::+          dt: 1391230800000          = January 1, 19970 00:00:00 UTC
::+          dt: 1391230000000+800000   = January 1, 19970 00:00:00 UTC
::+          dt:'1391230800000'         = January 1, 19970 00:00:00 UTC
::+          dt:'1391230000000+800000'  = error
::+
::+      String timestamp representation
::+        - Any string accepted by the JScript Date.Parse() method.
::+            See http://msdn.microsoft.com/en-us/library/k4w173wk(v=vs.84).aspx
::+
::+        Examples: All of the following represent Midnight on January 4, 2013
::+                  assuming local time zone is U.S Eastern Standard Time (EST)
::+
::+          dt:'1-4-2013'                  Defaults to local time zone
::+          dt:'January 4, 2013 EST'       Explicit Eastern Std Time (US)
::+          dt:'2013/1/4 -05'              Explicit Eastern Std Time (US)
::+          dt:'Jan 3 2013 23: CST'        Central Standard Time (US)
::+          dt:'2013 3 Jan 9:00 pm -0800'  Pacific Standard Time (US)
::+          dt:'01/04/2013 05:00:00 UTC'   Universal Coordinated Time
::+          dt:'1/4/2013 05:30 +0530'      India Standard Time
::+
::+      File timestamps to millisecond accuracy using WMI. Very slow, but
::+      locale agnostic. WMI call may not work on some older machines.
::+        - 'created'   = Creation date/time of the file
::+        - 'modified'  = Last Modified date/time of the file
::+        - 'accessed'  = Last Accessed date/time of the file
::+
::+        Example:
::+          dt:'created'  = creation date/time of the file to be renamed
::+
::+      File timestamps to second accuracy using FileSystemObject. Very fast,
::+      but locale dependent. May not parse properly for some countries.
::+        - 'fsoCreated'  = Creation date/time of the file
::+        - 'fsoModified' = Last Modified date/time of the file
::+        - 'fsoAccessed' = Last Accessed date/time of the file
::+
::+      Array with 2 to 7 numeric expressions (local time only)
::+        - [year,months,days,hours,minutes,seconds,milliseconds]
::+            year and months are required, the rest are optional
::+            Missing values are assumed to be 0
::+            Missing values are not allowed between specified values
::+            month 0 = January
::+            day 1 = First day of month, day 0 = Last day of prior month
::+
::+          Examples:
::+            dt:[2014,3,1,17,30,22,457]  = April 1, 2014, 17:30:22.457
::+            dt:[2014,0,1]               = January 1, 2014, 00:00:00
::+            dt:[2014,0]                 = December 31, 2013, 00:00:00
::+            dt:[2014,,10]               = error
::+
::+  Date/Time offsets and time zone options all use the same syntax.
::+  The value represents a numeric offset for the specified time unit.
::+  It may be expressed as a numeric expression (math allowed), or
::+  a numeric string (no math allowed). Both positive and negative
::+  values may be used.
::+
::+    oy:  Year offset
::+    om:  Months offset
::+    od:  Days offset
::+    oh:  Hours offset
::+    on:  Minutes offset
::+    os:  Seconds offset
::+    of:  Milliseconds (Fractional seconds) offset
::+
::+    tz:  Time zone used for output = minutes offset from UTC
::+
::+  The fmt: option is a string that specifies the format of the output.
::+  Strings within curly braces are replaced by dynamic components that are
::+  derived from the computed time stamp. Strings within braces that do not
::+  match a fmt: component are left as is. Strings not in braces are left
::+  as is. The format component names are not case senstive.
::+
::+  For example, a U.S. date would be represented as fmt:'{yyyy}/{mm}/{dd}'
::+
::+  The default format is '{ISOTS}', which yields YYYYMMDDThhmmss.fff+hhmm
::+
::+    {YYYY}  4 digit year, zero padded
::+
::+    {YY}    2 digit year, zero padded
::+
::+    {Y}     year without zero padding
::+
::+    {MONTH} month name
::+
::+    {MTH}   month abbreviation
::+
::+    {MM}    2 digit month, zero padded
::+
::+    {M}     month without zero padding
::+
::+    {WEEKDAY} day of week name
::+
::+    {WKD}   day of week abbreviation
::+
::+    {W}     day of week number, 0=Sunday
::+
::+    {DD}    2 digit day, zero padded
::+
::+    {D}     day without zero padding
::+
::+    {HH}    2 digit hours, 24 hour format, zero padded
::+
::+    {H}     hours, 24 hour format without zero padding
::+
::+    {HH12}  2 digit hours, 12 hour format, zero padded
::+
::+    {H12}   hours, 12 hour format without zero padding
::+
::+    {NN}    2 digit minutes, zero padded
::+
::+    {N}     minutes without padding
::+
::+    {SS}    2 digit seconds, zero padded
::+
::+    {S}     seconds without padding
::+
::+    {FFF}   3 digit milliseconds, zero padded
::+
::+    {F}     milliseconds without padding
::+
::+    {AM}    AM or PM in upper case
::+
::+    {PM}    am or pm in lower case
::+
::+    {ZZZZ}  timezone expressed as minutes offset from UTC,
::+            zero padded to 3 digits with sign
::+
::+    {Z}     timzone minutes offset from UTC without padding
::+
::+    {ZS}    timezone sign
::+
::+    {ZH}    timezone hours hours offset from UTC, (no sign),
::+            padded to 2 digits
::+
::+    {ZM}    timezone minutes offset from UTC (no sign),
::+            padded to 2 digits
::+
::+    {ISOTS} YYYYMMDDThhmmss.fff+hhss
::+            Compressed ISO 8601 date/time (timestamp) with milliseconds
::+            and time zone
::+
::+    {ISODT} YYYYMMDD
::+            Compressed ISO 8601 date format
::+
::+    {ISOTM} hhmmss.fff
::+            Compressed ISO 8601 time format with milliseconds
::+
::+    {ISOTZ} +hhmm
::+            Compressed ISO 8601 timezone format
::+
::+    {ISO-TS} YYYY-MM-DDThh:mm:ss.fff+hh:ss
::+             ISO 8601 date/time (timestamp) with milliseconds and time zone
::+
::+    {ISO-DT} YYYY-MM-DD
::+             ISO 8601 date format
::+
::+    {ISO-TM} hh:mm:ss.fff
::+             ISO 8601 time format with milliseconds
::+
::+    {ISO-TZ} +hh:mm
::+             ISO 8601 timezone
::+
::+    {U}     Unix Epoch time: same as {US}
::+            Seconds since 1970-01-01 00:00:00 UTC.
::+            Negative numbers represent dates prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UMS}   Milliseconds since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {US}    Seconds since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UM}    Minutes since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UH}    Hours since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UD}    Days since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {USD}   Decimal seconds since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UMD}   Decimal minutes since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UHD}   Decimal hours since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UDD}   Decimal days since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {{}     A { character
::+
::+  The following options override the default English names for the months
::+  and days of the week. The value for each option is a space delimited list
::+  of names or abbreviations.
::+
::+    wkd: Day of week abbreviations
::+         default = 'Sun Mon Tue Wed Thu Fri Sat'
::+
::+    weekday: Day of week names
::+         default = 'Sunday Monday Tuesday Wednesday Thursday Friday'
::+
::+    mth: Month abbreviations
::+         default = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'
::+
::+    month: Month names
::+         default = 'January February March April May Jun July August'
::+                 + ' September October November December'
::+
============= :Batch portion ============
@echo off
setlocal disableDelayedExpansion

if .%2 equ . (
  set "test=%~1"
  setlocal enableDelayedExpansion
  if "!test:~0,1!" equ "-" set "test=/!test:~1!"
  if "!test!" equ "/?" (
    for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
    exit /b 0
  ) else if "!test!" equ "/??" 2>nul (
    (for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A)|more /e
    exit /b 0
  ) else if /i "!test!" equ "/?ts()" (
    for /f "tokens=* delims=:+" %%A in ('findstr "^::+" "%~f0"') do @echo(%%A
    exit /b 0
  ) else if /i "!test!" equ "/??ts()" 2>nul (
    (for /f "tokens=* delims=:+" %%A in ('findstr "^::+" "%~f0"') do @echo(%%A)|more /e
    exit /b 0
  ) else if /i "!test!" equ "/?regex" (
    explorer "http://msdn.microsoft.com/en-us/library/ae5bf541(v=vs.80).aspx"
    exit /b 0
  ) else if /i "!test!" equ "/?replace" (
    explorer "http://msdn.microsoft.com/en-US/library/efy6s3e6(v=vs.80).aspx"
    exit /b 0
  ) else if /i "!test!" equ "/?version" (
    for /f "tokens=* delims=:" %%A in ('findstr "^::JREN\.BAT" "%~f0"') do @echo(%%A
    exit /b 0
  ) else (
    call :err "Insufficient arguments"
    exit /b 2
  )
)

:: Define options
set "options= /D: /FM:"" /FX:"" /I: /J: /L: /LIST: /NBEG:1 /NINC:1 /NPAD:3 /P:. /PM:"" /PX:"" /RFM:"" /RFX:"" /RPM:"" /RPX:"" /Q: /S: /T: /U: "

:: Set default option values
for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"

:: Get options
:loop
if not "%~3"=="" (
  set "test=%~3"
  setlocal enableDelayedExpansion
  if "!test:~0,1!" equ "-" set "test=/!test:~1!"
  if "!test:~0,1!" neq "/" (
    call :err "Too many arguments"
    exit /b 2
  )
  for /f "delims=" %%A in ("!test!") do (
    set "test=!options:*%%A:=! "
    if "!test!"=="!options! " (
        endlocal
        call :err "Invalid option %~3"
        exit /b 2
    ) else if "!test:~0,1!"==" " (
        endlocal
        set "%%A=1"
        if /i "%%A" equ "/L" set "/U="
        if /i "%%A" equ "/U" set "/L="
    ) else (
        endlocal
        if %4. equ . (
          call :err "Missing %~3 value"
          exit /b 2
        )
        set "%%A=%~4"
        shift /3
    )
  )
  shift /3
  goto :loop
)

:: Execute
cscript //E:JScript //nologo "%~f0" %1 %2
exit /b %errorlevel%

:err
>&2 (
  echo ERROR: %~1
)
exit /b

************* JScript portion **********/
var $n
var _g=new Object();
try {

  _g.defineReplFunc=function() {
    eval(_g.replFunc);
  }

  _g.main=function() {

    function err( msg, rtn ) {
      WScript.StdErr.WriteLine(msg);
      if (rtn) WScript.Quit(rtn);
    }

    function BuildRegex( loc, regex, options ) {
      try {
        return regex ? new RegExp( regex, options ) : false;
      } catch(e) {
        err( 'Invalid '+loc+' regular expression: '+e.message, 1);
      }
    }

    function GetInt( loc, numStr, minVal ) {
      var n = parseInt( numStr );
      if (isNaN(n) || n<minVal) {
        err( 'Error: Invalid '+loc+' value', 1 );
      }
      return n;
    }

    var env = WScript.CreateObject("WScript.Shell").Environment("Process"),
        fso = new ActiveXObject("Scripting.FileSystemObject");

    try {
      var root = fso.GetFolder( env('/P') );
    } catch(e) {
      err( 'Invalid /P path: '+e.message, 1 );
    }

    function MaskRepl($0) {
      switch ($0) {
        case '/P:':
        case '/p:': return root.Path.replace(/[.^$*+?()[{\\|]/g,"\\$&");
        case '**':  return '.*';
        case '*':   return '[^\\\\]*';
        case '?':   return '[^\\\\.]?';
        default:    return '\\'+$0;
      }
    }

    var args=WScript.Arguments,
        search = BuildRegex( 'Search', args.Item(0), env('/I')?'gi':'g' ),
        replace=args.Item(1),
        rMask = BuildRegex( '/RFM', env('/RFM'), 'i' ),
        rExclude = BuildRegex( '/RFX', env('/RFX'), 'i' ),
        rPathMask = BuildRegex( '/RPM', env('/RPM'), 'i' ),
        rPathExclude = BuildRegex( '/RPX', env('/RPX'), 'i' ),
        regex = new RegExp("/P:|[*][*]|[*]|[?]|[.^$+()[{\\\\]","ig");
        mask = env('/FM') ? new RegExp( '^(?:' + env('/FM').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        exclude = env('/FX') ? new RegExp( '^(?:' + env('/FX').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        pathMask = env('/PM') ? new RegExp( '^(?:' + env('/PM').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        pathExclude = env('/PX') ? new RegExp( '^(?:' + env('/PX').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        upper = env('/U'),
        lower = env('/L'),
        recurse = env('/S'),
        jscript = env('/J'),
        list = env('/LIST'),
        beg = GetInt( '/NBEG', env('/NBEG'), 0 ),
        inc = GetInt( '/NINC', env('/NINC'), 1 ),
        pad = GetInt( '/NPAD', env('/NPAD'), 1 ),
        padStr = Array( pad+1 ).join('0'),
        test = env('/T'),
        quiet = env('/Q');

    _g.dirs = env('/D');

    function ProcessFolder( folder ) {
      var i, a=[];
      var num = beg;
      if (recurse || _g.dirs) {
        var folders = new Enumerator(folder.SubFolders);
        for( i=0 ; !folders.atEnd(); folders.moveNext()) {
          a[i++]=folders.item()
          if (recurse) ProcessFolder(folders.item());
        }
      }
      if ( (!pathMask || pathMask.test(folder.Path)) &&
           (!rPathMask || rPathMask.test(folder.Path)) &&
           (!pathExclude || !pathExclude.test(folder.Path)) &&
           (!rPathExclude || !rPathExclude.test(folder.Path)) ) {
        if (!_g.dirs) {
          a=[];
          var files = new Enumerator(folder.Files);
          for (i=0; !files.atEnd(); files.moveNext()) a[i++]=files.item();
        }
        for (i=0; i<a.length; i++) {
          _g.file = a[i];
          _g.wmiFile = undefined;
          var oldName = a[i].Name;
          if ( (!mask || mask.test(oldName)) &&
               (!rMask || rMask.test(oldName)) &&
               (!exclude || !exclude.test(oldName)) &&
               (!rExclude || !rExclude.test(oldName)) ) {
            if (jscript) {
              $n = num.toString();
              if ($n.length<pad) $n = (padStr+$n).slice(-pad);
            }
            try {
              var newName = oldName.replace( search, replace );
            } catch(e) {
              err( 'Replace error: '+e.message, 1 );
            }
            if (!list) newName=newName.replace( /[ .]+$/, "" );
            if (jscript && !list && newName.search(/[<>|:/\\*?"\x00-\x1F]/)>=0) err('Error: Invalid file name character in Replace',1);
            if (upper) newName = uc(newName);
            if (lower) newName = lc(newName);
            if (newName != oldName || list) {
              try {
                var oldPath=a[i].Path;
                if (!test && !list) {
                  if (a[i].Name.toUpperCase() == newName.toUpperCase()) a[i].Name = '_{JREN_tempName}_';
                  a[i].Name = newName;
                }
                if (list) WScript.echo(newName);
                else if (!quiet) WScript.echo( '"'+oldPath+'"  -->  "'+newName+'"' );
              } catch(e) {
                if (a[i].Name != oldName) a[i].Name = oldName;
                err( 'Unable to rename "'+a[i].Path+'"  -->  "'+newName+'" : "'+e.message, 0 );
              }
              num+=inc;
            }
          }
        }
      }
    }

    if (jscript) {
      var regex=new RegExp('.|'+search,''),
          cnt;
      'x'.replace( regex, function(){cnt=arguments.length-2; return '';} );
      _g.replFunc='_g.replFunc=function($0';
      for (var i=1; i<cnt; i++) _g.replFunc+=',$'+i;
      _g.replFunc+=',$off,$src){return eval(_g.replace);}';
      _g.defineReplFunc();
      _g.replace = replace;
      replace = _g.replFunc;
    } else if (!list && replace.search(/[<>|:/\\*?"\x00-\x1F]/)>=0) err('Error: Invalid file name character in Replace',1);

    ProcessFolder( root );
    WScript.Quit(0);
  }

  _g.main();

} catch(e) {
  WScript.StdErr.WriteLine("JScript runtime error: "+e.message);
  WScript.Quit(1);
}

function lc(str) { return str.toLowerCase(); }

function uc(str) { return str.toUpperCase(); }

function lpad( val, pad ) {
  if (!pad) pad='';
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (pad+rtn).slice(-pad.length) : val;
}

function rpad( val, pad ) {
  if (!pad) pad='';
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (rtn+pad).slice(0,pad.length) : val;
}

function ts(opt) {
  if (opt===undefined) opt={};
  if (opt.constructor !== Object) badOp('ts()');
  if (!opt.wkd)     opt.wkd='Sun Mon Tue Wed Thu Fri Sat';
  if (!opt.weekday) opt.weekday='Sunday Monday Tuesday Wednesday Thursday Friday Saturday';
  if (!opt.mth)     opt.mth='Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec';
  if (!opt.month)   opt.month='January February March April May June July August September October November December';
  if (!opt.fmt)     opt.fmt='{isots}';

  var wkd     = opt.wkd.split(' '),
      weekday = opt.weekday.split(' '),
      mth     = opt.mth.split(' '),
      month   = opt.month.split(' '),
      y,m,d,w,h,h12,n,s,f,u,z,zs,za, dt,
      sp=' ', ps='/', pc=':', pd='-', pp='.', p2='00', p3='000', p4='0000';
  if (wkd.length!=7)     badOp('wkd');
  if (weekday.length!=7) badOp('weekday');
  if (mth.length!=12)    badOp('mth');
  if (month.length!=12)  badOp('month');

  dt = getDt(opt.dt);
  if (opt.oy) dt.setFullYear(     dt.getFullYear()    +getNum('oy'));
  if (opt.om) dt.setMonth(        dt.getMonth()       +getNum('om'));
  if (opt.od) dt.setDate(         dt.getDate()        +getNum('od'));
  if (opt.oh) dt.setHours(        dt.getHours()       +getNum('oh'));
  if (opt.on) dt.setMinutes(      dt.getMinutes()     +getNum('on'));
  if (opt.os) dt.setSeconds(      dt.getSeconds()     +getNum('os'));
  if (opt.of) dt.setMilliseconds( dt.getMilliseconds()+getNum('of'));
  if (opt.tz) dt.setMinutes(      dt.getMinutes()  +(z=getNum('tz')));

  y = opt.tz!==undefined ? dt.getUTCFullYear(): dt.getFullYear();
  m = opt.tz!==undefined ? dt.getUTCMonth()   : dt.getMonth();
  d = opt.tz!==undefined ? dt.getUTCDate()    : dt.getDate();
  w = opt.tz!==undefined ? dt.getUTCDay()     : dt.getDay();
  h = opt.tz!==undefined ? dt.getUTCHours()   : dt.getHours();
  n = opt.tz!==undefined ? dt.getUTCMinutes() : dt.getMinutes();
  s = opt.tz!==undefined ? dt.getUTCSeconds() : dt.getSeconds();
  f = opt.tz!==undefined ? dt.getUTCMilliseconds() : dt.getMilliseconds();
  u = dt.getTime();

  h12 = h%12;
  if (!h12) h12=12;

  if (!opt.tz) z=-dt.getTimezoneOffset();
  zs = z<0 ? '-' : '+';
  za = Math.abs(z);

  return opt.fmt.replace( /\{(.*?)\}/gi, repl );

  function getNum( v ) {
    var rtn = Number(opt[v]);
    if (isNaN(rtn-rtn)) badOp(v);
    return rtn;
  }

  function getDt( v ) {
    var dt, n;
    if (v===undefined) {
      dt = new Date();
    } else switch (v.constructor) {
      case Date:
      case Number:
        dt = new Date(v);
        break;
      case Array:
        try {dt=eval( 'new Date('+v.join(',')+')' )} catch(e){}
        break;
      case String:
        switch (v.toLowerCase()) {
          case '':         dt = new Date(); break;
          case 'created':  dt = getWmiDt('c'); break;
          case 'modified': dt = getWmiDt('m'); break;
          case 'accessed': dt = getWmiDt('a'); break;
          case 'fsocreated':  dt = new Date(_g.file.DateCreated); break;
          case 'fsomodified': dt = new Date(_g.file.DateLastModified); break;
          case 'fsoaccessed': dt = new Date(_g.file.DateLastAccessed); break;
          default:
            n=Number(v);
            if (isNaN(n-n)) dt = new Date(v);
            else            dt = new Date(n);
            break;
        }
        break;
    }
    if (isNaN(dt)) badOp('dt');
    return dt;
  }
 
  function badOp(option) {
    throw new Error('Invalid '+option+' value');
  }

  function trunc( n ) { return Math[n>0?"floor":"ceil"](n); }
 
  function repl($0,$1) {
    switch ($1.toUpperCase()) {
      case 'YYYY' : return lpad(y,p4);
      case 'YY'   : return (p2+y.toString()).slice(-2);
      case 'Y'    : return y.toString();
      case 'MM'   : return lpad(m+1,p2);
      case 'M'    : return (m+1).toString();
      case 'DD'   : return lpad(d,p2);
      case 'D'    : return d.toString();
      case 'W'    : return w.toString();
      case 'HH'   : return lpad(h,p2);
      case 'H'    : return h.toString();
      case 'HH12' : return lpad(h12,p2);
      case 'H12'  : return h12.toString();
      case 'NN'   : return lpad(n,p2);
      case 'N'    : return n.toString();
      case 'SS'   : return lpad(s,p2);
      case 'S'    : return s.toString();
      case 'FFF'  : return lpad(f,p3);
      case 'F'    : return f.toString();
      case 'AM'   : return h>=12 ? 'PM' : 'AM';
      case 'PM'   : return h>=12 ? 'pm' : 'am';
      case 'UMS'  : return u.toString();
      case 'USD'  : return (u/1000).toString();
      case 'UMD'  : return (u/1000/60).toString();
      case 'UHD'  : return (u/1000/60/60).toString();
      case 'UDD'  : return (u/1000/60/60/24).toString();
      case 'U'    : return trunc(u/1000).toString();
      case 'US'   : return trunc(u/1000).toString();
      case 'UM'   : return trunc(u/1000/60).toString();
      case 'UH'   : return trunc(u/1000/60/60).toString();
      case 'UD'   : return trunc(u/1000/60/60/24).toString();
      case 'ZZZZ' : return zs+lpad(za,p3);
      case 'Z'    : return z.toString();
      case 'ZS'   : return zs;
      case 'ZH'   : return lpad(trunc(za/60),p2);
      case 'ZM'   : return lpad(za%60,p2);
      case 'ISOTS'  : return ''+lpad(y,p4)+lpad(m+1,p2)+lpad(d,p2)+'T'+lpad(h,p2)+lpad(n,p2)+lpad(s,p2)+pp+lpad(f,p3)+zs+lpad(trunc(za/60),p2)+lpad(za%60,p2);
      case 'ISODT'  : return ''+lpad(y,p4)+lpad(m+1,p2)+lpad(d,p2);
      case 'ISOTM'  : return ''+lpad(h,p2)+lpad(n,p2)+lpad(s,p2)+pp+lpad(f,p3);
      case 'ISOTZ'  : return ''+zs+lpad(trunc(za/60),p2)+lpad(za%60,p2);
      case 'ISO-TS' : return ''+lpad(y,p4)+pd+lpad(m+1,p2)+pd+lpad(d,p2)+'T'+lpad(h,p2)+pc+lpad(n,p2)+pc+lpad(s,p2)+pp+lpad(f,p3)+zs+lpad(trunc(za/60),p2)+pc+lpad(za%60,p2);
      case 'ISO-DT' : return ''+lpad(y,p4)+pd+lpad(m+1,p2)+pd+lpad(d,p2);
      case 'ISO-TM' : return ''+lpad(h,p2)+pc+lpad(n,p2)+pc+lpad(s,p2)+pp+lpad(f,p3);
      case 'ISO-TZ' : return ''+zs+lpad(trunc(za/60),p2)+pc+lpad(za%60,p2);
      case 'WEEKDAY': return weekday[w];
      case 'WKD'    : return wkd[w];
      case 'MONTH'  : return month[m];
      case 'MTH'    : return mth[m];
      case '{'      : return $1;
      default       : return $0;
    }
  }

  function getWmiDt( prop ) {
    if (_g.wmi===undefined) {
      var svcLoc = new ActiveXObject("WbemScripting.SWbemLocator");
      _g.wmi = svcLoc.ConnectServer(".", "root\\cimv2");
    }
    if (_g.wmiFile===undefined) {
      _g.wmiFile = new Enumerator(_g.wmi.ExecQuery(
                                    "Select * From "+(_g.dirs?"Win32_Directory":"Cim_DataFile")
                                     +" Where Name = '"+_g.file.Path.replace(/\\/g,"\\\\").replace(/'/g,"\\'")+"'"
                                  )).item();
    }
    var wmiDt;
    switch (prop.toLowerCase()) {
      case 'c': wmiDt=_g.wmiFile.CreationDate; break;
      case 'm': wmiDt=_g.wmiFile.LastModified; break;
      case 'a': wmiDt=_g.wmiFile.LastAccessed; break;
      default: return undefined;
    }
    var tz=Number(wmiDt.substr(22));
    var dt = new Date(     wmiDt.substr(0,4)+ps+wmiDt.substr(4,2)+ps+wmiDt.substr(6,2)
                       +sp+wmiDt.substr(8,2)+pc+wmiDt.substr(10,2)+pc+wmiDt.substr(12,2)
                       +sp+wmiDt.substr(21,1)+lpad(trunc(tz/60),p2)+lpad(tz%60,p2)
                     );
    dt.setMilliseconds(Number(wmiDt.substr(15,3)));
    return dt;
  }

}

function attr(off) {
  var a=_g.file.Attributes;
  var o=off?off.substr(0,1):'';
  return  (a&1   ?'R':o?o:'r')  //Read only
         +(a&2   ?'H':o?o:'h')  //Hidden
         +(a&4   ?'S':o?o:'s')  //System
         +(a&32  ?'A':o?o:'a')  //Archive
         +(a&1024?'L':o?o:'l')  //Link or Shortcut
         +(a&2048?'C':o?o:'c'); //Compressed
}

function size(pad) {return lpad(_g.file.Size,pad);}

function type(pad) {return rpad(_g.file.Type,pad);}

function path(pad) {return rpad(_g.file.Path,pad);}

function parent(pad) {return rpad(_g.file.ParentFolder.Path,pad);}

function name(pad) {return rpad(_g.file.Name,pad);}

function sPath(pad) {return rpad(_g.file.ShortPath,pad);}

function sParent(pad) {return rpad(_g.file.ParentFolder.ShortPath,pad);}

function sName(pad) {return rpad(_g.file.ShortName,pad);}


Dave Benham

Squashman
Expert
Posts: 4486
Joined: 23 Dec 2011 13:59

Re: JREN.BAT - Rename files/folders using regular expression

#17 Post by Squashman » 24 Nov 2015 14:13

Dave, I have still yet to master even the basics of any of your regular expression utilities. I use RegExp all the time in SAS yet find it difficult to understand how to use them in any of your hybrid jscript utilities. It should all carry over but I think it may just me not understanding the syntax of how to use your scripts.

Was trying to figure out how to use JREN to solve this SO question today. I gave up after an hour of banging my head against the wall.

http://stackoverflow.com/questions/3390 ... characters

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: JREN.BAT - Rename files/folders using regular expressions

#18 Post by dbenham » 27 Nov 2015 20:10

StackOverflow Question wrote:
How can I rename a file to a file name stripped of all other characters except letters and numbers?

For example:

"It's_A Brand (New) #Year,,2016!.txt" renamed to "ItsABrandNewYear2016.txt"

Is it possible to do this with Windows batch?

@Squashman

If you didn't have to worry about the extension dot, then it would be simple:

Code: Select all

jren "[^a-z0-9]" "" /i /fm "It's_A Brand (New) #Year,,2016!.txt"

Note that the File Mask (/FM) is totally independent of the find and replace.

Here is an exact solution that uses the negative look ahead feature to preserve the extension dot:

Code: Select all

jren "[^a-z0-9.]|\.(?!txt$)" "" /i /fm "It's_A Brand (New) #Year,,2016!.txt"


And here is a variant that removes all non-alphanumeric characters, except the extension dot, from all files in the current folder:

Code: Select all

jren "[^a-z0-9.]|\.(?![^.]+$)" "" /i


Dave Benham

MatthewGrantAU
Posts: 2
Joined: 06 Mar 2016 22:58

Re: JREN.BAT - Rename files/folders using regular expressions

#19 Post by MatthewGrantAU » 06 Mar 2016 23:24

Hi,

I am trying to do a variant of this date parsing example that was posted above:

> old name: "Some multiple word title Mon dd, yyyy - Some multiple word description.pdf"
> new name: "yyyy-mm-dd - Some multiple word title - Some multiple word description.pdf"
> Code:

Code: Select all

C:\test>jren "^(.* )([A-Z][a-z]{2} \d\d?, \d\d\d\d) -" "ts({dt:$2,fmt:'{iso-dt}'})+' - '+$1+'-'" /j /fm *.pdf


My filename is always: YYYY-MM-DD description of file.txt (eg. 2016-03-07 description of file.txt)
and I need: YYYYMMM description of file.txt (eg. 2016Mar description of file.txt)

my code is:

Code: Select all

jren "(\d{4}-\d{2}-\d{2})(.*\.txt)" "ts({dt:$1,fmt:'{YYYY}{MTH}'})+$2" -t -j
(test mode)

but I get: Replace Error: Invalid dt value

It seems the dt:$1 is the problem, as it cannot determine the date format it is being passed. I have tested with filenames using different date formatting (eg. ddd dd, yyyy) and it works ok.
How can I tell dt that the incoming string is YYYY-MM-DD, and not YYYY-DD-MM or something else?

thanks,
Matt

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: JREN.BAT - Rename files/folders using regular expressions

#20 Post by dbenham » 07 Mar 2016 06:34

MatthewGrantAU wrote:How can I tell dt that the incoming string is YYYY-MM-DD, and not YYYY-DD-MM or something else?

You can't. JREN uses the JScript Date.parse() method to convert a string into a date. It is flexible, but non configurable. See http://technet.microsoft.com/en-us/libr ... wk(v=vs.84).aspx for a description of the rules for what it will accept.

But you already know the source date format, so you can easily parse it yourself.

You can specify the date as an array of values, remembering to subtract 1 from the month to make it 0 based:

Code: Select all

jren "^(\d{4})-(\d{2})-(\d{2})(?!\d)" "ts({ dt:[$1,$2-1,$3], fmt:'{YYYY}{MTH}' })" /t /j /fm *.txt

Or you can build your own string that ts() can recognize:

Code: Select all

jren "^(\d{4})-(\d{2})-(\d{2})(?!\d)" "ts({ dt:$2+'/'+$3+'/'+$1, fmt:'{YYYY}{MTH}' })" /t /j /fm *.txt

Note that in both cases:
  • I use ^ to only match files that begin with the date.
  • I use the (?!\d) look-ahead assertion to make sure that the next character after the date is not another digit, else we are probably not dealing with a date.
  • Rather than matching the remainder of the file name and including it in the replacement string, I use the /FM file mask option to restrict the search to .txt files.

Since we are only replacing the date portion of the name, you can add the uc() function to convert the month abbreviation into all caps, if wanted:

Code: Select all

jren "^(\d{4})-(\d{2})-(\d{2})(?!\d)" "uc(ts({ dt:[$1,$2-1,$3], fmt:'{YYYY}{MTH}' }))" /t /j /fm *.txt


Use JREN /? to get general help, and JREN /?TS() to get help on the ts() function.


Dave Benham

MatthewGrantAU
Posts: 2
Joined: 06 Mar 2016 22:58

Re: JREN.BAT - Rename files/folders using regular expressions

#21 Post by MatthewGrantAU » 07 Mar 2016 19:32

Hi Dave.

thank you so much for the quick reply.

While waiting, I have been reading and then re-reading "jren /?ts()" until I was cross-eyed
and in the last ten minutes finally discovered what Date.Parse() was looking for! ( and the fact that dt: could accept a string like $1+$2)

so I built

Code: Select all

"^(\d{4})-(\d\d)-(\d\d)" "ts({dt:$2+'-'+$3+'-'+$1,fmt:'{YYYY}{MTH}'})"  /j /fm *.txt

which worked! :D

I very much appreciate the suggestion of adding the (?!\d) for looking ahead. The files come from a website, exported in the "yyyy-mm-dd ..." format, but will add that to my code to be thorough.
And the uc() is great! (I wanted CAPS for mth, and had cheated by editing the code in jren.bat to JAN, FEB, ...) I did not realise that I could make the uc() only affect the ts() function.

THANKS!!

regards,
Matt

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: JREN.BAT - Rename files/folders using regular expressions

#22 Post by dbenham » 09 Jul 2016 14:16

MatthewGrantAU wrote:(I wanted CAPS for mth, and had cheated by editing the code in jren.bat to JAN, FEB, ...)

There should never be a need to edit the code to modify the names of the days or months. There are built in ts() options to specify whatever values you want for dayOfWeek/month names/abbreviations.

From the ts() help:

Code: Select all

  The following options override the default English names for the months
  and days of the week. The value for each option is a space delimited list
  of names or abbreviations.

    wkd: Day of week abbreviations
         default = 'Sun Mon Tue Wed Thu Fri Sat'

    weekday: Day of week names
         default = 'Sunday Monday Tuesday Wednesday Thursday Friday'

    mth: Month abbreviations
         default = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'

    month: Month names
         default = 'January February March April May Jun July August'
                 + ' September October November December'

I opted to use the uc() function, but I could have just as easily redefined the month abbreviations using the mth option


Dave Benham

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: JREN.BAT - Rename files/folders using regular expressions

#23 Post by dbenham » 10 Jul 2016 12:53

Here is version 2.6 of JREN.BAT

I have fixed a bug in the ts() function dealing with date/time offsets. The offsets now specify precise time intervals that are not dependent on local daylight savings conditions - they are always relative to UTC time, which ignores daylight savings. (Note - I had attempted to do this in release 2.5, but I screwed that up. So I deleted that post and jumped to version 2.6)

I have also aded the olm: and old: ts() options that support local month and local day offsets in a way that the local hour never changes. These options do adjust for local daylight savings conditions, so the absolute offset may not be an even multiple of 24 hours. 1 hour less will be added if going from standard to daylight savings, and 1 hour more will be added if going form daylight savings to standard.

JREN.BAT version 2.6

Code: Select all

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
@goto :Batch

::JREN.BAT version 2.6 by Dave Benham
::
::  Release History:
::    2.6  2016-07-10: Fixed bug in ts() offset computations when the before and
::                     after have different timezones due to daylight savings.
::                     Added olm: and old: options to the ts() function to allow
::                     the addition of months/years without changing the local hour.
::                     In other words, the computation compensates for changes in
::                     dayling savings conditions.
::    2.4  2015-07-15: Added /?? and /??TS() paged help options
::    2.3  2015-03-03: Ignore invalid characters if /LIST mode
::                     Bug fix - abort if invalid character with /T mode
::    2.2  2014-12-10: Bug fix - forgot to make the search regex global
::    2.1  2014-12-10: Additional dt: options for FileSystemObject timestamps
::    2.0  2014-12-07: New /LIST option.
::                     Added ts() function and /?ts() documentation
::                     Many new JScript functions for use with /J with /LIST
::                     Added GOTO at top to improve startup performance
::    1.1  2014-12-02: Options may be prefaced with / or -
::                     Corrected some documentation
::    1.0  2014-11-30: Initial release
::
::============ Documentation ===========
:::
:::JREN  Search  Replace  [/Option  [Value]]...
:::JREN  /?[REGEX|REPLACE|VERSION|TS()]
:::JREN  /??[ts()]
:::
:::  Rename files in the current directory by performing a regular expression
:::  search/replace on the old file name to generate the new file name.
:::  This includes read only, hidden, and system files.
:::
:::  Search  - By default, this is a case sensitive JScript (ECMA) regular
:::            expression expressed as a string. The search is applied globally
:::            to the entire file name.
:::
:::            JScript regex syntax documentation is available at
:::            http://msdn.microsoft.com/en-us/library/ae5bf541(v=vs.80).aspx
:::
:::  Replace - By default, this is the string to be used as a replacement for
:::            each found search expression. Full support is provided for
:::            substituion patterns available to the JScript replace method.
:::
:::            For example, $& represents the portion of the source that matched
:::            the entire search pattern, $1 represents the first captured
:::            submatch, $2 the second captured submatch, etc. A $ literal
:::            can be escaped as $$.
:::
:::            An empty replacement string must be represented as "".
:::
:::            Replace substitution pattern syntax is fully documented at
:::            http://msdn.microsoft.com/en-US/library/efy6s3e6(v=vs.80).aspx
:::
:::  Options:  Behavior may be altered by appending one or more options.
:::  The option names are case insensitive, and may appear in any order
:::  after the Replace argument. Options may be prefaced with / or -
:::
:::      /D  - Rename Directories instead of files.
:::
:::      /FM FileOrFolderMask
:::
:::            Only rename files or folders that match any of the pattern(s)
:::            using standard wildcards. Multiple patterns are delimited by a
:::            pipe (|). Only complete name matches count.
:::
:::              * matches any 0 or more characters
:::              ? matches any 0 or 1 character except .
:::
:::      /FX FileOrFolderExclusion
:::
:::            Exclude files or folders that match any of the pattern(s)
:::            using standard wildcards. Multiple patterns are delimited by a
:::            pipe (|). Only complete name matches count.
:::
:::              * matches any 0 or more characters
:::              ? matches any 0 or 1 character except .
:::
:::      /I  - Ignore case when matching.
:::
:::      /J  - Treat Replace as a JScript expression.
:::            The following variables contain details about each match:
:::
:::              $0 = the substring that matched the Search
:::              $1 through $n = captured submatch strings
:::              $off = the offset where the match occurred
:::              $src = the original source string
:::
:::            The following are also available:
:::
:::              $n = An incrementing number for use in the name. The value
:::                   is reset to the /NBEG value for each directory.
:::                   It increases by the /NINC value for each renamed file.
:::                   The value may be zero padded to the width specified by
:::                   the /NPAD value.
:::
:::              lc(str)
:::
:::                 Convert str to lower case. Shorthand for str.toLowerCase().
:::
:::              uc(str)
:::
:::                 Convert str to upper case. Shorthand for str.toUpperCase().
:::
:::              lpad(string,pad)
:::
:::                 Used to left pad string str to a minimum length. If the
:::                 str already has length >= the pad string length, then no
:::                 change is made. Otherwise it left pads the value with the
:::                 characters of the pad string to the length of pad.
:::
:::                 Examples:
:::                    lpad(15,'0000')    returns "0015"
:::                    lpad(15,'    ')    returns "  15"
:::                    lpad(19011,'0000') returns "19011"
:::
:::              rpad(string,pad)
:::
:::                 Used to right pad the string to a minimum length. If the
:::                 str already has length >= the pad string length, then no
:::                 change is made. Otherwise it right pads the value with the
:::                 characters of the pad string to the length of pad.
:::
:::              ts( {option:value, option:value...} )
:::
:::                 Perform date/time computations and produce formatted
:::                 timestamps. This function can get the current date/and time,
:::                 or get the created/lastModified/lastAccessed timestamps for
:::                 the file being renamed, or parse a date/time from the name
:::                 of the file, or use a user specified value.
:::
:::                 Use JREN /?TS() to get help on the ts() function
:::
:::              attr( [offChar] )
:::
:::                 Lists the attributes of the file/folder. Set attributes are
:::                 listed in upper case and unset attributes are shown as the
:::                 offChar, or lower case. The listed attributes are:
:::                   R - Read Only
:::                   H - Hidden
:::                   S - System
:::                   A - Archive
:::                   L - Link or Shortcut
:::                   C - Compressed
:::
:::              size( [pad] )
:::
:::                 File/folder size, optionally left padded to the length of
:::                 the pad string.
:::
:::              type( [pad] )
:::
:::                 File/folder type, optionally right padded to the length of
:::                 the pad string.
:::
:::              name( [pad] )
:::
:::                 Name of the file/folder, optionally right padded to
:::                 the length of the pad string.
:::
:::              path( [pad] )
:::
:::                 Full Path of file/folder, optionally right padded to
:::                 the length of the pad string.
:::
:::              parent( [pad] )
:::
:::                 Path of the parent folder, optionally right padded to
:::                 the length of the pad string.
:::
:::              sName( [pad ])
:::
:::                 Short (8.3) name of the file/folder, optionally right padded
:::                 to the length of the pad string.
:::
:::              sPath( [pad] )
:::
:::                 Path of the file/folder using short (8.3) names, optionally
:::                 right padded to the length of the pad string.
:::
:::              sParent( [pad] )
:::
:::                 Path of the parent folder using short (8.3) names,
:::                 optionally right padded to the length of the pad string.
:::
:::      /L  - Convert names to Lower case. Entire names can be converted to
:::            lower case without any other changes by using empty strings ("")
:::            for both Search and Replace.
:::
:::      /LIST - List the Rename results only, without quotes, and without
:::            renaming anything. Invalid file name characters are ignored.
:::            The resulting "name" is always listed, even if no change.
:::
:::      /NBEG BeginValue
:::
:::            Specifies the initial $n value for each directory. The value
:::            must be an integer >= 0. The default value is 1.
:::
:::      /NINC IncrementValue
:::
:::            Specifies the amount $n is incremented after each rename.
:::            The value must be an integer >=1. The default value is 1.
:::
:::      /NPAD MinWidth
:::
:::            Specifies the minimum width for each $n value. If the $n value
:::            has fewer digits than MinWidth, then the value is zero padded
:::            on the left to achieve the MinWidth. The value must be >= 1.
:::            The default value is 3.
:::
:::      /P RootPath
:::
:::            Specifies the path where the rename is to take place.
:::            The default of . represents the current directory.
:::            Wildcards are not allowed.
:::
:::      /PM PathMask
:::
:::            Only rename files or folders whose parent folder path matches
:::            any of the PathMask pattern(s) using augmented wildcards.
:::            Multiple patterns are delimited by a pipe (|). Only full path
:::            matches count.
:::
:::              /P:  matches the root path specified by option /P
:::              **   matches any 0 or more characters
:::              *    matches any 0 or more characters except \
:::              ?    matches any 0 or 1 character except . or \
:::
:::           This option is only useful if the /S option is used.
:::
:::      /PX PathExclusion
:::
:::            Exclude files or folders whose parent folder path matches any
:::            of the PathExclusion pattern(s) using augmented wildcards.
:::            Multiple patterns are delimited by a pipe (|). Only full path
:::            matches count.
:::
:::              /P:  matches the root path specified by option /P
:::              **   matches any 0 or more characters
:::              *    matches any 0 or more characters except \
:::              ?    matches any 0 or 1 character except . or \
:::
:::           This option is only useful if the /S option is used.
:::
:::      /Q  - Do not list the renamed files/folders (Quiet mode).
:::
:::      /RFM RegexFileOrFoldereMask
:::
:::            Only rename files or folders that match the regular expression,
:::            ignoring case. Partial name matches count.
:::
:::      /RFX RegexFileOrFolderExclusion
:::
:::            Exclude files or folders that match the regular Expression,
:::            ignoring case. Partial name matches count.
:::
:::      /RPM RegexPathMask
:::
:::            Only rename files or folders whose parent folder path matches the
:::            RegexPathMask regular expression, ignoring case. Partial path
:::            matches count. This option is really only useful if the /S
:::            option is used.
:::
:::      /RPX RegexPathExclusion
:::
:::            Exclude files or folders whose parent folder path matches the
:::            RegexPathExclusion regular expression, ignoring case. Partial
:::            path matches count. This option is really only useful if the
:::            /S option is used.
:::
:::      /S  - Recurse Subdirectories.
:::
:::      /T  - List the rename operations that would be attempted,
:::            but do not rename anything. (Test mode)
:::
:::      /U  - Convert names to Upper case. Entire names can be converted to
:::            upper case without any other changes by using empty strings ("")
:::            for both Search and Replace.
:::
:::  Help is available by supplying a single argument beginning with /?:
:::
:::      /?        - Writes this help documentation to stdout.
:::      /??       - Same as /? except uses MORE to provide pagination
:::
:::      /?REGEX   - Opens up Microsoft's JScript regular expression
:::                  documentation within your browser.
:::
:::      /?REPLACE - Opens up Microsoft's JScript REPLACE documentation
:::                  within your browser.
:::
:::      /?VERSION - Writes the JREN version number to stdout.
:::
:::      /?TS()    - Writes documentation for the ts() function to stdout.
:::      /??TS()   - Same as /??TS() except uses MORE for pagination.
:::
:::  JREN.BAT was written by Dave Benham, and originally posted at
:::  http://www.dostips.com/forum/viewtopic.php?f=3&t=6081
:::
:: =============== ts() documentation ===============
::+
::+ts( [ { [option:value [,option:value]...] } ] )
::+ 
::+  A JScript function that can performs date and time computations and return
::+  a formatted time string.
::+ 
::+  The option object argument within curly braces is optional - if no argument
::+  is given, then it returns the current timestamp using compressed ISO 8601
::+  format with milliseconds and local time zone - YYYYMMDDThhmmss.fff+zzzz
::+ 
::+    Examples:
::+      ts()  - Current date/time:  YYYYMMDDThhmmss.fff+zzzz
::+      ts({dt:'created',fmt:'{iso-dt}'})  - The file create date:  YYYY-MM-DD
::+      ts({od:-1,fmt:'{YYYY}_{MM}_{DD}'}) - Yesterday's date:  YYYY_MM_DD
::+ 
::+  Option names are case sensitive. There are 5 types of options:
::+    1) Specify the base date          - dt:
::+    2) Specify date/time offsets      - oy: om: od: oh: on: os: of:
::+    3) Specify the output time zone   - tz:
::+    4) Specify the output format      - fmt:
::+    5) Configure the day-of-week and  - wkd: weekday: mth: month:
::+       month names for non-English
::+       users
::+
::+  Specify the base date and time
::+
::+    dt:  Value specifies the base date and time. Many formats supported:
::+
::+      Current local date/time
::+        - do not specify a dt: value
::+        - undefined value
::+        - empty string ''
::+
::+        Examples:
::+          dt:''
::+          dt:undefined
::+
::+      Milliseconds since 1970-01-01 00:00:00 UTC
::+        - NumericExpression (math OK)
::+        - NumericString (no math)
::+
::+        Examples:
::+          dt: 1391230800000          = January 1, 19970 00:00:00 UTC
::+          dt: 1391230000000+800000   = January 1, 19970 00:00:00 UTC
::+          dt:'1391230800000'         = January 1, 19970 00:00:00 UTC
::+          dt:'1391230000000+800000'  = error
::+
::+      String timestamp representation
::+        - Any string accepted by the JScript Date.Parse() method.
::+            See http://msdn.microsoft.com/en-us/library/k4w173wk(v=vs.84).aspx
::+
::+        Examples: All of the following represent Midnight on January 4, 2013
::+                  assuming local time zone is U.S Eastern Standard Time (EST)
::+
::+          dt:'1-4-2013'                  Defaults to local time zone
::+          dt:'January 4, 2013 EST'       Explicit Eastern Std Time (US)
::+          dt:'2013/1/4 -05'              Explicit Eastern Std Time (US)
::+          dt:'Jan 3 2013 23: CST'        Central Standard Time (US)
::+          dt:'2013 3 Jan 9:00 pm -0800'  Pacific Standard Time (US)
::+          dt:'01/04/2013 05:00:00 UTC'   Universal Coordinated Time
::+          dt:'1/4/2013 05:30 +0530'      India Standard Time
::+
::+      File timestamps to millisecond accuracy using WMI. Very slow, but
::+      locale agnostic. WMI call may not work on some older machines.
::+        - 'created'   = Creation date/time of the file
::+        - 'modified'  = Last Modified date/time of the file
::+        - 'accessed'  = Last Accessed date/time of the file
::+
::+        Example:
::+          dt:'created'  = creation date/time of the file to be renamed
::+
::+      File timestamps to second accuracy using FileSystemObject. Very fast,
::+      but locale dependent. May not parse properly for some countries.
::+        - 'fsoCreated'  = Creation date/time of the file
::+        - 'fsoModified' = Last Modified date/time of the file
::+        - 'fsoAccessed' = Last Accessed date/time of the file
::+
::+      Array with 2 to 7 numeric expressions (local time only)
::+        - [year,months,days,hours,minutes,seconds,milliseconds]
::+            year and months are required, the rest are optional
::+            Missing values are assumed to be 0
::+            Missing values are not allowed between specified values
::+            month 0 = January
::+            day 1 = First day of month, day 0 = Last day of prior month
::+
::+          Examples:
::+            dt:[2014,3,1,17,30,22,457]  = April 1, 2014, 17:30:22.457
::+            dt:[2014,0,1]               = January 1, 2014, 00:00:00
::+            dt:[2014,0]                 = December 31, 2013, 00:00:00
::+            dt:[2014,,10]               = error
::+
::+  Date/Time offsets and time zone options all use the same syntax.
::+  The value represents a numeric offset for the specified time unit.
::+  It may be expressed as a numeric expression (math allowed), or
::+  a numeric string (no math allowed). Both positive and negative
::+  values may be used.
::+
::+    oy:  Year offset
::+    om:  Months offset
::+    od:  Days offset
::+    oh:  Hours offset
::+    on:  Minutes offset
::+    os:  Seconds offset
::+    of:  Milliseconds (Fractional seconds) offset
::+
::+    olm: LocalMonths offset (adjusted for changes in daylight savings)
::+    old: LocalDays offset (adjusted for changes in daylight savings)
::+
::+    tz:  Time zone used for output = minutes offset from UTC
::+
::+  The fmt: option is a string that specifies the format of the output.
::+  Strings within curly braces are replaced by dynamic components that are
::+  derived from the computed time stamp. Strings within braces that do not
::+  match a fmt: component are left as is. Strings not in braces are left
::+  as is. The format component names are not case senstive.
::+
::+  For example, a U.S. date would be represented as fmt:'{yyyy}/{mm}/{dd}'
::+
::+  The default format is '{ISOTS}', which yields YYYYMMDDThhmmss.fff+hhmm
::+
::+    {YYYY}  4 digit year, zero padded
::+
::+    {YY}    2 digit year, zero padded
::+
::+    {Y}     year without zero padding
::+
::+    {MONTH} month name
::+
::+    {MTH}   month abbreviation
::+
::+    {MM}    2 digit month, zero padded
::+
::+    {M}     month without zero padding
::+
::+    {WEEKDAY} day of week name
::+
::+    {WKD}   day of week abbreviation
::+
::+    {W}     day of week number, 0=Sunday
::+
::+    {DD}    2 digit day, zero padded
::+
::+    {D}     day without zero padding
::+
::+    {HH}    2 digit hours, 24 hour format, zero padded
::+
::+    {H}     hours, 24 hour format without zero padding
::+
::+    {HH12}  2 digit hours, 12 hour format, zero padded
::+
::+    {H12}   hours, 12 hour format without zero padding
::+
::+    {NN}    2 digit minutes, zero padded
::+
::+    {N}     minutes without padding
::+
::+    {SS}    2 digit seconds, zero padded
::+
::+    {S}     seconds without padding
::+
::+    {FFF}   3 digit milliseconds, zero padded
::+
::+    {F}     milliseconds without padding
::+
::+    {AM}    AM or PM in upper case
::+
::+    {PM}    am or pm in lower case
::+
::+    {ZZZZ}  timezone expressed as minutes offset from UTC,
::+            zero padded to 3 digits with sign
::+
::+    {Z}     timzone minutes offset from UTC without padding
::+
::+    {ZS}    timezone sign
::+
::+    {ZH}    timezone hours hours offset from UTC, (no sign),
::+            padded to 2 digits
::+
::+    {ZM}    timezone minutes offset from UTC (no sign),
::+            padded to 2 digits
::+
::+    {ISOTS} YYYYMMDDThhmmss.fff+hhss
::+            Compressed ISO 8601 date/time (timestamp) with milliseconds
::+            and time zone
::+
::+    {ISODT} YYYYMMDD
::+            Compressed ISO 8601 date format
::+
::+    {ISOTM} hhmmss.fff
::+            Compressed ISO 8601 time format with milliseconds
::+
::+    {ISOTZ} +hhmm
::+            Compressed ISO 8601 timezone format
::+
::+    {ISO-TS} YYYY-MM-DDThh:mm:ss.fff+hh:ss
::+             ISO 8601 date/time (timestamp) with milliseconds and time zone
::+
::+    {ISO-DT} YYYY-MM-DD
::+             ISO 8601 date format
::+
::+    {ISO-TM} hh:mm:ss.fff
::+             ISO 8601 time format with milliseconds
::+
::+    {ISO-TZ} +hh:mm
::+             ISO 8601 timezone
::+
::+    {U}     Unix Epoch time: same as {US}
::+            Seconds since 1970-01-01 00:00:00 UTC.
::+            Negative numbers represent dates prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UMS}   Milliseconds since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {US}    Seconds since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UM}    Minutes since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UH}    Hours since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UD}    Days since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {USD}   Decimal seconds since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UMD}   Decimal minutes since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UHD}   Decimal hours since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UDD}   Decimal days since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {{}     A { character
::+
::+  The following options override the default English names for the months
::+  and days of the week. The value for each option is a space delimited list
::+  of names or abbreviations.
::+
::+    wkd: Day of week abbreviations
::+         default = 'Sun Mon Tue Wed Thu Fri Sat'
::+
::+    weekday: Day of week names
::+         default = 'Sunday Monday Tuesday Wednesday Thursday Friday'
::+
::+    mth: Month abbreviations
::+         default = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'
::+
::+    month: Month names
::+         default = 'January February March April May Jun July August'
::+                 + ' September October November December'
::+
============= :Batch portion ============
@echo off
setlocal disableDelayedExpansion

if .%2 equ . (
  set "test=%~1"
  setlocal enableDelayedExpansion
  if "!test:~0,1!" equ "-" set "test=/!test:~1!"
  if "!test!" equ "/?" (
    for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
    exit /b 0
  ) else if "!test!" equ "/??" 2>nul (
    (for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A)|more /e
    exit /b 0
  ) else if /i "!test!" equ "/?ts()" (
    for /f "tokens=* delims=:+" %%A in ('findstr "^::+" "%~f0"') do @echo(%%A
    exit /b 0
  ) else if /i "!test!" equ "/??ts()" 2>nul (
    (for /f "tokens=* delims=:+" %%A in ('findstr "^::+" "%~f0"') do @echo(%%A)|more /e
    exit /b 0
  ) else if /i "!test!" equ "/?regex" (
    explorer "http://msdn.microsoft.com/en-us/library/ae5bf541(v=vs.80).aspx"
    exit /b 0
  ) else if /i "!test!" equ "/?replace" (
    explorer "http://msdn.microsoft.com/en-US/library/efy6s3e6(v=vs.80).aspx"
    exit /b 0
  ) else if /i "!test!" equ "/?version" (
    for /f "tokens=* delims=:" %%A in ('findstr "^::JREN\.BAT" "%~f0"') do @echo(%%A
    exit /b 0
  ) else (
    call :err "Insufficient arguments"
    exit /b 2
  )
)

:: Define options
set "options= /D: /FM:"" /FX:"" /I: /J: /L: /LIST: /NBEG:1 /NINC:1 /NPAD:3 /P:. /PM:"" /PX:"" /RFM:"" /RFX:"" /RPM:"" /RPX:"" /Q: /S: /T: /U: "

:: Set default option values
for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"

:: Get options
:loop
if not "%~3"=="" (
  set "test=%~3"
  setlocal enableDelayedExpansion
  if "!test:~0,1!" equ "-" set "test=/!test:~1!"
  if "!test:~0,1!" neq "/" (
    call :err "Too many arguments"
    exit /b 2
  )
  for /f "delims=" %%A in ("!test!") do (
    set "test=!options:*%%A:=! "
    if "!test!"=="!options! " (
        endlocal
        call :err "Invalid option %~3"
        exit /b 2
    ) else if "!test:~0,1!"==" " (
        endlocal
        set "%%A=1"
        if /i "%%A" equ "/L" set "/U="
        if /i "%%A" equ "/U" set "/L="
    ) else (
        endlocal
        if %4. equ . (
          call :err "Missing %~3 value"
          exit /b 2
        )
        set "%%A=%~4"
        shift /3
    )
  )
  shift /3
  goto :loop
)

:: Execute
cscript //E:JScript //nologo "%~f0" %1 %2
exit /b %errorlevel%

:err
>&2 (
  echo ERROR: %~1
)
exit /b

************* JScript portion **********/
var $n
var _g=new Object();
try {

  _g.defineReplFunc=function() {
    eval(_g.replFunc);
  }

  _g.main=function() {

    function err( msg, rtn ) {
      WScript.StdErr.WriteLine(msg);
      if (rtn) WScript.Quit(rtn);
    }

    function BuildRegex( loc, regex, options ) {
      try {
        return regex ? new RegExp( regex, options ) : false;
      } catch(e) {
        err( 'Invalid '+loc+' regular expression: '+e.message, 1);
      }
    }

    function GetInt( loc, numStr, minVal ) {
      var n = parseInt( numStr );
      if (isNaN(n) || n<minVal) {
        err( 'Error: Invalid '+loc+' value', 1 );
      }
      return n;
    }

    var env = WScript.CreateObject("WScript.Shell").Environment("Process"),
        fso = new ActiveXObject("Scripting.FileSystemObject");

    try {
      var root = fso.GetFolder( env('/P') );
    } catch(e) {
      err( 'Invalid /P path: '+e.message, 1 );
    }

    function MaskRepl($0) {
      switch ($0) {
        case '/P:':
        case '/p:': return root.Path.replace(/[.^$*+?()[{\\|]/g,"\\$&");
        case '**':  return '.*';
        case '*':   return '[^\\\\]*';
        case '?':   return '[^\\\\.]?';
        default:    return '\\'+$0;
      }
    }

    var args=WScript.Arguments,
        search = BuildRegex( 'Search', args.Item(0), env('/I')?'gi':'g' ),
        replace=args.Item(1),
        rMask = BuildRegex( '/RFM', env('/RFM'), 'i' ),
        rExclude = BuildRegex( '/RFX', env('/RFX'), 'i' ),
        rPathMask = BuildRegex( '/RPM', env('/RPM'), 'i' ),
        rPathExclude = BuildRegex( '/RPX', env('/RPX'), 'i' ),
        regex = new RegExp("/P:|[*][*]|[*]|[?]|[.^$+()[{\\\\]","ig");
        mask = env('/FM') ? new RegExp( '^(?:' + env('/FM').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        exclude = env('/FX') ? new RegExp( '^(?:' + env('/FX').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        pathMask = env('/PM') ? new RegExp( '^(?:' + env('/PM').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        pathExclude = env('/PX') ? new RegExp( '^(?:' + env('/PX').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        upper = env('/U'),
        lower = env('/L'),
        recurse = env('/S'),
        jscript = env('/J'),
        list = env('/LIST'),
        beg = GetInt( '/NBEG', env('/NBEG'), 0 ),
        inc = GetInt( '/NINC', env('/NINC'), 1 ),
        pad = GetInt( '/NPAD', env('/NPAD'), 1 ),
        padStr = Array( pad+1 ).join('0'),
        test = env('/T'),
        quiet = env('/Q');

    _g.dirs = env('/D');

    function ProcessFolder( folder ) {
      var i, a=[];
      var num = beg;
      if (recurse || _g.dirs) {
        var folders = new Enumerator(folder.SubFolders);
        for( i=0 ; !folders.atEnd(); folders.moveNext()) {
          a[i++]=folders.item()
          if (recurse) ProcessFolder(folders.item());
        }
      }
      if ( (!pathMask || pathMask.test(folder.Path)) &&
           (!rPathMask || rPathMask.test(folder.Path)) &&
           (!pathExclude || !pathExclude.test(folder.Path)) &&
           (!rPathExclude || !rPathExclude.test(folder.Path)) ) {
        if (!_g.dirs) {
          a=[];
          var files = new Enumerator(folder.Files);
          for (i=0; !files.atEnd(); files.moveNext()) a[i++]=files.item();
        }
        for (i=0; i<a.length; i++) {
          _g.file = a[i];
          _g.wmiFile = undefined;
          var oldName = a[i].Name;
          if ( (!mask || mask.test(oldName)) &&
               (!rMask || rMask.test(oldName)) &&
               (!exclude || !exclude.test(oldName)) &&
               (!rExclude || !rExclude.test(oldName)) ) {
            if (jscript) {
              $n = num.toString();
              if ($n.length<pad) $n = (padStr+$n).slice(-pad);
            }
            try {
              var newName = oldName.replace( search, replace );
            } catch(e) {
              err( 'Replace error: '+e.message, 1 );
            }
            if (!list) newName=newName.replace( /[ .]+$/, "" );
            if (jscript && !list && newName.search(/[<>|:/\\*?"\x00-\x1F]/)>=0) err('Error: Invalid file name character in Replace',1);
            if (upper) newName = uc(newName);
            if (lower) newName = lc(newName);
            if (newName != oldName || list) {
              try {
                var oldPath=a[i].Path;
                if (!test && !list) {
                  if (a[i].Name.toUpperCase() == newName.toUpperCase()) a[i].Name = '_{JREN_tempName}_';
                  a[i].Name = newName;
                }
                if (list) WScript.echo(newName);
                else if (!quiet) WScript.echo( '"'+oldPath+'"  -->  "'+newName+'"' );
              } catch(e) {
                if (a[i].Name != oldName) a[i].Name = oldName;
                err( 'Unable to rename "'+a[i].Path+'"  -->  "'+newName+'" : "'+e.message, 0 );
              }
              num+=inc;
            }
          }
        }
      }
    }

    if (jscript) {
      var regex=new RegExp('.|'+search,''),
          cnt;
      'x'.replace( regex, function(){cnt=arguments.length-2; return '';} );
      _g.replFunc='_g.replFunc=function($0';
      for (var i=1; i<cnt; i++) _g.replFunc+=',$'+i;
      _g.replFunc+=',$off,$src){return eval(_g.replace);}';
      _g.defineReplFunc();
      _g.replace = replace;
      replace = _g.replFunc;
    } else if (!list && replace.search(/[<>|:/\\*?"\x00-\x1F]/)>=0) err('Error: Invalid file name character in Replace',1);

    ProcessFolder( root );
    WScript.Quit(0);
  }

  _g.main();

} catch(e) {
  WScript.StdErr.WriteLine("JScript runtime error: "+e.message);
  WScript.Quit(1);
}

function lc(str) { return str.toLowerCase(); }

function uc(str) { return str.toUpperCase(); }

function lpad( val, pad ) {
  if (!pad) pad='';
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (pad+rtn).slice(-pad.length) : val;
}

function rpad( val, pad ) {
  if (!pad) pad='';
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (rtn+pad).slice(0,pad.length) : val;
}

function ts(opt) {
  if (opt===undefined) opt={};
  if (opt.constructor !== Object) badOp('ts()');
  if (!opt.wkd)     opt.wkd='Sun Mon Tue Wed Thu Fri Sat';
  if (!opt.weekday) opt.weekday='Sunday Monday Tuesday Wednesday Thursday Friday Saturday';
  if (!opt.mth)     opt.mth='Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec';
  if (!opt.month)   opt.month='January February March April May June July August September October November December';
  if (!opt.fmt)     opt.fmt='{isots}';

  var wkd     = opt.wkd.split(' '),
      weekday = opt.weekday.split(' '),
      mth     = opt.mth.split(' '),
      month   = opt.month.split(' '),
      y,m,d,w,h,h12,n,s,f,u,z,zs,za, dt,
      sp=' ', ps='/', pc=':', pd='-', pp='.', p2='00', p3='000', p4='0000';
  if (wkd.length!=7)     badOp('wkd');
  if (weekday.length!=7) badOp('weekday');
  if (mth.length!=12)    badOp('mth');
  if (month.length!=12)  badOp('month');

  dt = getDt(opt.dt);
  if (opt.oy) dt.setUTCFullYear(     dt.getUTCFullYear()    +getNum('oy'));
  if (opt.om) dt.setUTCMonth(        dt.getUTCMonth()       +getNum('om'));
  if (opt.od) dt.setUTCDate(         dt.getUTCDate()        +getNum('od'));
  if (opt.oh) dt.setUTCHours(        dt.getUTCHours()       +getNum('oh'));
  if (opt.on) dt.setUTCMinutes(      dt.getUTCMinutes()     +getNum('on'));
  if (opt.os) dt.setUTCSeconds(      dt.getUTCSeconds()     +getNum('os'));
  if (opt.of) dt.setUTCMilliseconds( dt.getUTCMilliseconds()+getNum('of'));
  if (opt.tz) dt.setUTCMinutes(      dt.getUTCMinutes()  +(z=getNum('tz')));

  if (opt.olm) dt.setMonth(        dt.getMonth()       +getNum('olm'));
  if (opt.old) dt.setDate(         dt.getDate()        +getNum('old'));

  y = opt.tz!==undefined ? dt.getUTCFullYear(): dt.getFullYear();
  m = opt.tz!==undefined ? dt.getUTCMonth()   : dt.getMonth();
  d = opt.tz!==undefined ? dt.getUTCDate()    : dt.getDate();
  w = opt.tz!==undefined ? dt.getUTCDay()     : dt.getDay();
  h = opt.tz!==undefined ? dt.getUTCHours()   : dt.getHours();
  n = opt.tz!==undefined ? dt.getUTCMinutes() : dt.getMinutes();
  s = opt.tz!==undefined ? dt.getUTCSeconds() : dt.getSeconds();
  f = opt.tz!==undefined ? dt.getUTCMilliseconds() : dt.getMilliseconds();
  u = dt.getTime();

  h12 = h%12;
  if (!h12) h12=12;

  if (!opt.tz) z=-dt.getTimezoneOffset();
  zs = z<0 ? '-' : '+';
  za = Math.abs(z);

  return opt.fmt.replace( /\{(.*?)\}/gi, repl );

  function getNum( v ) {
    var rtn = Number(opt[v]);
    if (isNaN(rtn-rtn)) badOp(v);
    return rtn;
  }

  function getDt( v ) {
    var dt, n;
    if (v===undefined) {
      dt = new Date();
    } else switch (v.constructor) {
      case Date:
      case Number:
        dt = new Date(v);
        break;
      case Array:
        try {dt=eval( 'new Date('+v.join(',')+')' )} catch(e){}
        break;
      case String:
        switch (v.toLowerCase()) {
          case '':         dt = new Date(); break;
          case 'created':  dt = getWmiDt('c'); break;
          case 'modified': dt = getWmiDt('m'); break;
          case 'accessed': dt = getWmiDt('a'); break;
          case 'fsocreated':  dt = new Date(_g.file.DateCreated); break;
          case 'fsomodified': dt = new Date(_g.file.DateLastModified); break;
          case 'fsoaccessed': dt = new Date(_g.file.DateLastAccessed); break;
          default:
            n=Number(v);
            if (isNaN(n-n)) dt = new Date(v);
            else            dt = new Date(n);
            break;
        }
        break;
    }
    if (isNaN(dt)) badOp('dt');
    return dt;
  }
 
  function badOp(option) {
    throw new Error('Invalid '+option+' value');
  }

  function trunc( n ) { return Math[n>0?"floor":"ceil"](n); }
 
  function repl($0,$1) {
    switch ($1.toUpperCase()) {
      case 'YYYY' : return lpad(y,p4);
      case 'YY'   : return (p2+y.toString()).slice(-2);
      case 'Y'    : return y.toString();
      case 'MM'   : return lpad(m+1,p2);
      case 'M'    : return (m+1).toString();
      case 'DD'   : return lpad(d,p2);
      case 'D'    : return d.toString();
      case 'W'    : return w.toString();
      case 'HH'   : return lpad(h,p2);
      case 'H'    : return h.toString();
      case 'HH12' : return lpad(h12,p2);
      case 'H12'  : return h12.toString();
      case 'NN'   : return lpad(n,p2);
      case 'N'    : return n.toString();
      case 'SS'   : return lpad(s,p2);
      case 'S'    : return s.toString();
      case 'FFF'  : return lpad(f,p3);
      case 'F'    : return f.toString();
      case 'AM'   : return h>=12 ? 'PM' : 'AM';
      case 'PM'   : return h>=12 ? 'pm' : 'am';
      case 'UMS'  : return u.toString();
      case 'USD'  : return (u/1000).toString();
      case 'UMD'  : return (u/1000/60).toString();
      case 'UHD'  : return (u/1000/60/60).toString();
      case 'UDD'  : return (u/1000/60/60/24).toString();
      case 'U'    : return trunc(u/1000).toString();
      case 'US'   : return trunc(u/1000).toString();
      case 'UM'   : return trunc(u/1000/60).toString();
      case 'UH'   : return trunc(u/1000/60/60).toString();
      case 'UD'   : return trunc(u/1000/60/60/24).toString();
      case 'ZZZZ' : return zs+lpad(za,p3);
      case 'Z'    : return z.toString();
      case 'ZS'   : return zs;
      case 'ZH'   : return lpad(trunc(za/60),p2);
      case 'ZM'   : return lpad(za%60,p2);
      case 'ISOTS'  : return ''+lpad(y,p4)+lpad(m+1,p2)+lpad(d,p2)+'T'+lpad(h,p2)+lpad(n,p2)+lpad(s,p2)+pp+lpad(f,p3)+zs+lpad(trunc(za/60),p2)+lpad(za%60,p2);
      case 'ISODT'  : return ''+lpad(y,p4)+lpad(m+1,p2)+lpad(d,p2);
      case 'ISOTM'  : return ''+lpad(h,p2)+lpad(n,p2)+lpad(s,p2)+pp+lpad(f,p3);
      case 'ISOTZ'  : return ''+zs+lpad(trunc(za/60),p2)+lpad(za%60,p2);
      case 'ISO-TS' : return ''+lpad(y,p4)+pd+lpad(m+1,p2)+pd+lpad(d,p2)+'T'+lpad(h,p2)+pc+lpad(n,p2)+pc+lpad(s,p2)+pp+lpad(f,p3)+zs+lpad(trunc(za/60),p2)+pc+lpad(za%60,p2);
      case 'ISO-DT' : return ''+lpad(y,p4)+pd+lpad(m+1,p2)+pd+lpad(d,p2);
      case 'ISO-TM' : return ''+lpad(h,p2)+pc+lpad(n,p2)+pc+lpad(s,p2)+pp+lpad(f,p3);
      case 'ISO-TZ' : return ''+zs+lpad(trunc(za/60),p2)+pc+lpad(za%60,p2);
      case 'WEEKDAY': return weekday[w];
      case 'WKD'    : return wkd[w];
      case 'MONTH'  : return month[m];
      case 'MTH'    : return mth[m];
      case '{'      : return $1;
      default       : return $0;
    }
  }

  function getWmiDt( prop ) {
    if (_g.wmi===undefined) {
      var svcLoc = new ActiveXObject("WbemScripting.SWbemLocator");
      _g.wmi = svcLoc.ConnectServer(".", "root\\cimv2");
    }
    if (_g.wmiFile===undefined) {
      _g.wmiFile = new Enumerator(_g.wmi.ExecQuery(
                                    "Select * From "+(_g.dirs?"Win32_Directory":"Cim_DataFile")
                                     +" Where Name = '"+_g.file.Path.replace(/\\/g,"\\\\").replace(/'/g,"\\'")+"'"
                                  )).item();
    }
    var wmiDt;
    switch (prop.toLowerCase()) {
      case 'c': wmiDt=_g.wmiFile.CreationDate; break;
      case 'm': wmiDt=_g.wmiFile.LastModified; break;
      case 'a': wmiDt=_g.wmiFile.LastAccessed; break;
      default: return undefined;
    }
    var tz=Number(wmiDt.substr(22));
    var dt = new Date(     wmiDt.substr(0,4)+ps+wmiDt.substr(4,2)+ps+wmiDt.substr(6,2)
                       +sp+wmiDt.substr(8,2)+pc+wmiDt.substr(10,2)+pc+wmiDt.substr(12,2)
                       +sp+wmiDt.substr(21,1)+lpad(trunc(tz/60),p2)+lpad(tz%60,p2)
                     );
    dt.setMilliseconds(Number(wmiDt.substr(15,3)));
    return dt;
  }

}

function attr(off) {
  var a=_g.file.Attributes;
  var o=off?off.substr(0,1):'';
  return  (a&1   ?'R':o?o:'r')  //Read only
         +(a&2   ?'H':o?o:'h')  //Hidden
         +(a&4   ?'S':o?o:'s')  //System
         +(a&32  ?'A':o?o:'a')  //Archive
         +(a&1024?'L':o?o:'l')  //Link or Shortcut
         +(a&2048?'C':o?o:'c'); //Compressed
}

function size(pad) {return lpad(_g.file.Size,pad);}

function type(pad) {return rpad(_g.file.Type,pad);}

function path(pad) {return rpad(_g.file.Path,pad);}

function parent(pad) {return rpad(_g.file.ParentFolder.Path,pad);}

function name(pad) {return rpad(_g.file.Name,pad);}

function sPath(pad) {return rpad(_g.file.ShortPath,pad);}

function sParent(pad) {return rpad(_g.file.ParentFolder.ShortPath,pad);}

function sName(pad) {return rpad(_g.file.ShortName,pad);}


Dave Benham

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: JREN.BAT - Rename files/folders using regular expressions

#24 Post by dbenham » 26 Oct 2016 10:01

Version 2.7 fixes a ts() bug - it failed to return UTC time when setting time zone to 0 (tz:0)

Code: Select all

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
@goto :Batch

::JREN.BAT version 2.7 by Dave Benham
::
::  Release History:
::    2.7  2016-10-26: Fixed ts() bug - tz:0 was not working
::    2.6  2016-07-10: Fixed bug in ts() offset computations when the before and
::                     after have different timezones due to daylight savings.
::                     Added olm: and old: options to the ts() function to allow
::                     the addition of months/years without changing the local hour.
::                     In other words, the computation compensates for changes in
::                     dayling savings conditions.
::    2.4  2015-07-15: Added /?? and /??TS() paged help options
::    2.3  2015-03-03: Ignore invalid characters if /LIST mode
::                     Bug fix - abort if invalid character with /T mode
::    2.2  2014-12-10: Bug fix - forgot to make the search regex global
::    2.1  2014-12-10: Additional dt: options for FileSystemObject timestamps
::    2.0  2014-12-07: New /LIST option.
::                     Added ts() function and /?ts() documentation
::                     Many new JScript functions for use with /J with /LIST
::                     Added GOTO at top to improve startup performance
::    1.1  2014-12-02: Options may be prefaced with / or -
::                     Corrected some documentation
::    1.0  2014-11-30: Initial release
::
::============ Documentation ===========
:::
:::JREN  Search  Replace  [/Option  [Value]]...
:::JREN  /?[REGEX|REPLACE|VERSION|TS()]
:::JREN  /??[ts()]
:::
:::  Rename files in the current directory by performing a regular expression
:::  search/replace on the old file name to generate the new file name.
:::  This includes read only, hidden, and system files.
:::
:::  Search  - By default, this is a case sensitive JScript (ECMA) regular
:::            expression expressed as a string. The search is applied globally
:::            to the entire file name.
:::
:::            JScript regex syntax documentation is available at
:::            http://msdn.microsoft.com/en-us/library/ae5bf541(v=vs.80).aspx
:::
:::  Replace - By default, this is the string to be used as a replacement for
:::            each found search expression. Full support is provided for
:::            substituion patterns available to the JScript replace method.
:::
:::            For example, $& represents the portion of the source that matched
:::            the entire search pattern, $1 represents the first captured
:::            submatch, $2 the second captured submatch, etc. A $ literal
:::            can be escaped as $$.
:::
:::            An empty replacement string must be represented as "".
:::
:::            Replace substitution pattern syntax is fully documented at
:::            http://msdn.microsoft.com/en-US/library/efy6s3e6(v=vs.80).aspx
:::
:::  Options:  Behavior may be altered by appending one or more options.
:::  The option names are case insensitive, and may appear in any order
:::  after the Replace argument. Options may be prefaced with / or -
:::
:::      /D  - Rename Directories instead of files.
:::
:::      /FM FileOrFolderMask
:::
:::            Only rename files or folders that match any of the pattern(s)
:::            using standard wildcards. Multiple patterns are delimited by a
:::            pipe (|). Only complete name matches count.
:::
:::              * matches any 0 or more characters
:::              ? matches any 0 or 1 character except .
:::
:::      /FX FileOrFolderExclusion
:::
:::            Exclude files or folders that match any of the pattern(s)
:::            using standard wildcards. Multiple patterns are delimited by a
:::            pipe (|). Only complete name matches count.
:::
:::              * matches any 0 or more characters
:::              ? matches any 0 or 1 character except .
:::
:::      /I  - Ignore case when matching.
:::
:::      /J  - Treat Replace as a JScript expression.
:::            The following variables contain details about each match:
:::
:::              $0 = the substring that matched the Search
:::              $1 through $n = captured submatch strings
:::              $off = the offset where the match occurred
:::              $src = the original source string
:::
:::            The following are also available:
:::
:::              $n = An incrementing number for use in the name. The value
:::                   is reset to the /NBEG value for each directory.
:::                   It increases by the /NINC value for each renamed file.
:::                   The value may be zero padded to the width specified by
:::                   the /NPAD value.
:::
:::              lc(str)
:::
:::                 Convert str to lower case. Shorthand for str.toLowerCase().
:::
:::              uc(str)
:::
:::                 Convert str to upper case. Shorthand for str.toUpperCase().
:::
:::              lpad(string,pad)
:::
:::                 Used to left pad string str to a minimum length. If the
:::                 str already has length >= the pad string length, then no
:::                 change is made. Otherwise it left pads the value with the
:::                 characters of the pad string to the length of pad.
:::
:::                 Examples:
:::                    lpad(15,'0000')    returns "0015"
:::                    lpad(15,'    ')    returns "  15"
:::                    lpad(19011,'0000') returns "19011"
:::
:::              rpad(string,pad)
:::
:::                 Used to right pad the string to a minimum length. If the
:::                 str already has length >= the pad string length, then no
:::                 change is made. Otherwise it right pads the value with the
:::                 characters of the pad string to the length of pad.
:::
:::              ts( {option:value, option:value...} )
:::
:::                 Perform date/time computations and produce formatted
:::                 timestamps. This function can get the current date/and time,
:::                 or get the created/lastModified/lastAccessed timestamps for
:::                 the file being renamed, or parse a date/time from the name
:::                 of the file, or use a user specified value.
:::
:::                 Use JREN /?TS() to get help on the ts() function
:::
:::              attr( [offChar] )
:::
:::                 Lists the attributes of the file/folder. Set attributes are
:::                 listed in upper case and unset attributes are shown as the
:::                 offChar, or lower case. The listed attributes are:
:::                   R - Read Only
:::                   H - Hidden
:::                   S - System
:::                   A - Archive
:::                   L - Link or Shortcut
:::                   C - Compressed
:::
:::              size( [pad] )
:::
:::                 File/folder size, optionally left padded to the length of
:::                 the pad string.
:::
:::              type( [pad] )
:::
:::                 File/folder type, optionally right padded to the length of
:::                 the pad string.
:::
:::              name( [pad] )
:::
:::                 Name of the file/folder, optionally right padded to
:::                 the length of the pad string.
:::
:::              path( [pad] )
:::
:::                 Full Path of file/folder, optionally right padded to
:::                 the length of the pad string.
:::
:::              parent( [pad] )
:::
:::                 Path of the parent folder, optionally right padded to
:::                 the length of the pad string.
:::
:::              sName( [pad ])
:::
:::                 Short (8.3) name of the file/folder, optionally right padded
:::                 to the length of the pad string.
:::
:::              sPath( [pad] )
:::
:::                 Path of the file/folder using short (8.3) names, optionally
:::                 right padded to the length of the pad string.
:::
:::              sParent( [pad] )
:::
:::                 Path of the parent folder using short (8.3) names,
:::                 optionally right padded to the length of the pad string.
:::
:::      /L  - Convert names to Lower case. Entire names can be converted to
:::            lower case without any other changes by using empty strings ("")
:::            for both Search and Replace.
:::
:::      /LIST - List the Rename results only, without quotes, and without
:::            renaming anything. Invalid file name characters are ignored.
:::            The resulting "name" is always listed, even if no change.
:::
:::      /NBEG BeginValue
:::
:::            Specifies the initial $n value for each directory. The value
:::            must be an integer >= 0. The default value is 1.
:::
:::      /NINC IncrementValue
:::
:::            Specifies the amount $n is incremented after each rename.
:::            The value must be an integer >=1. The default value is 1.
:::
:::      /NPAD MinWidth
:::
:::            Specifies the minimum width for each $n value. If the $n value
:::            has fewer digits than MinWidth, then the value is zero padded
:::            on the left to achieve the MinWidth. The value must be >= 1.
:::            The default value is 3.
:::
:::      /P RootPath
:::
:::            Specifies the path where the rename is to take place.
:::            The default of . represents the current directory.
:::            Wildcards are not allowed.
:::
:::      /PM PathMask
:::
:::            Only rename files or folders whose parent folder path matches
:::            any of the PathMask pattern(s) using augmented wildcards.
:::            Multiple patterns are delimited by a pipe (|). Only full path
:::            matches count.
:::
:::              /P:  matches the root path specified by option /P
:::              **   matches any 0 or more characters
:::              *    matches any 0 or more characters except \
:::              ?    matches any 0 or 1 character except . or \
:::
:::           This option is only useful if the /S option is used.
:::
:::      /PX PathExclusion
:::
:::            Exclude files or folders whose parent folder path matches any
:::            of the PathExclusion pattern(s) using augmented wildcards.
:::            Multiple patterns are delimited by a pipe (|). Only full path
:::            matches count.
:::
:::              /P:  matches the root path specified by option /P
:::              **   matches any 0 or more characters
:::              *    matches any 0 or more characters except \
:::              ?    matches any 0 or 1 character except . or \
:::
:::           This option is only useful if the /S option is used.
:::
:::      /Q  - Do not list the renamed files/folders (Quiet mode).
:::
:::      /RFM RegexFileOrFoldereMask
:::
:::            Only rename files or folders that match the regular expression,
:::            ignoring case. Partial name matches count.
:::
:::      /RFX RegexFileOrFolderExclusion
:::
:::            Exclude files or folders that match the regular Expression,
:::            ignoring case. Partial name matches count.
:::
:::      /RPM RegexPathMask
:::
:::            Only rename files or folders whose parent folder path matches the
:::            RegexPathMask regular expression, ignoring case. Partial path
:::            matches count. This option is really only useful if the /S
:::            option is used.
:::
:::      /RPX RegexPathExclusion
:::
:::            Exclude files or folders whose parent folder path matches the
:::            RegexPathExclusion regular expression, ignoring case. Partial
:::            path matches count. This option is really only useful if the
:::            /S option is used.
:::
:::      /S  - Recurse Subdirectories.
:::
:::      /T  - List the rename operations that would be attempted,
:::            but do not rename anything. (Test mode)
:::
:::      /U  - Convert names to Upper case. Entire names can be converted to
:::            upper case without any other changes by using empty strings ("")
:::            for both Search and Replace.
:::
:::  Help is available by supplying a single argument beginning with /?:
:::
:::      /?        - Writes this help documentation to stdout.
:::      /??       - Same as /? except uses MORE to provide pagination
:::
:::      /?REGEX   - Opens up Microsoft's JScript regular expression
:::                  documentation within your browser.
:::
:::      /?REPLACE - Opens up Microsoft's JScript REPLACE documentation
:::                  within your browser.
:::
:::      /?VERSION - Writes the JREN version number to stdout.
:::
:::      /?TS()    - Writes documentation for the ts() function to stdout.
:::      /??TS()   - Same as /??TS() except uses MORE for pagination.
:::
:::  JREN.BAT was written by Dave Benham, and originally posted at
:::  http://www.dostips.com/forum/viewtopic.php?f=3&t=6081
:::
:: =============== ts() documentation ===============
::+
::+ts( [ { [option:value [,option:value]...] } ] )
::+ 
::+  A JScript function that can performs date and time computations and return
::+  a formatted time string.
::+ 
::+  The option object argument within curly braces is optional - if no argument
::+  is given, then it returns the current timestamp using compressed ISO 8601
::+  format with milliseconds and local time zone - YYYYMMDDThhmmss.fff+zzzz
::+ 
::+    Examples:
::+      ts()  - Current date/time:  YYYYMMDDThhmmss.fff+zzzz
::+      ts({dt:'created',fmt:'{iso-dt}'})  - The file create date:  YYYY-MM-DD
::+      ts({od:-1,fmt:'{YYYY}_{MM}_{DD}'}) - Yesterday's date:  YYYY_MM_DD
::+ 
::+  Option names are case sensitive. There are 5 types of options:
::+    1) Specify the base date          - dt:
::+    2) Specify date/time offsets      - oy: om: od: oh: on: os: of:
::+    3) Specify the output time zone   - tz:
::+    4) Specify the output format      - fmt:
::+    5) Configure the day-of-week and  - wkd: weekday: mth: month:
::+       month names for non-English
::+       users
::+
::+  Specify the base date and time
::+
::+    dt:  Value specifies the base date and time. Many formats supported:
::+
::+      Current local date/time
::+        - do not specify a dt: value
::+        - undefined value
::+        - empty string ''
::+
::+        Examples:
::+          dt:''
::+          dt:undefined
::+
::+      Milliseconds since 1970-01-01 00:00:00 UTC
::+        - NumericExpression (math OK)
::+        - NumericString (no math)
::+
::+        Examples:
::+          dt: 1391230800000          = January 1, 19970 00:00:00 UTC
::+          dt: 1391230000000+800000   = January 1, 19970 00:00:00 UTC
::+          dt:'1391230800000'         = January 1, 19970 00:00:00 UTC
::+          dt:'1391230000000+800000'  = error
::+
::+      String timestamp representation
::+        - Any string accepted by the JScript Date.Parse() method.
::+            See http://msdn.microsoft.com/en-us/library/k4w173wk(v=vs.84).aspx
::+
::+        Examples: All of the following represent Midnight on January 4, 2013
::+                  assuming local time zone is U.S Eastern Standard Time (EST)
::+
::+          dt:'1-4-2013'                  Defaults to local time zone
::+          dt:'January 4, 2013 EST'       Explicit Eastern Std Time (US)
::+          dt:'2013/1/4 -05'              Explicit Eastern Std Time (US)
::+          dt:'Jan 3 2013 23: CST'        Central Standard Time (US)
::+          dt:'2013 3 Jan 9:00 pm -0800'  Pacific Standard Time (US)
::+          dt:'01/04/2013 05:00:00 UTC'   Universal Coordinated Time
::+          dt:'1/4/2013 05:30 +0530'      India Standard Time
::+
::+      File timestamps to millisecond accuracy using WMI. Very slow, but
::+      locale agnostic. WMI call may not work on some older machines.
::+        - 'created'   = Creation date/time of the file
::+        - 'modified'  = Last Modified date/time of the file
::+        - 'accessed'  = Last Accessed date/time of the file
::+
::+        Example:
::+          dt:'created'  = creation date/time of the file to be renamed
::+
::+      File timestamps to second accuracy using FileSystemObject. Very fast,
::+      but locale dependent. May not parse properly for some countries.
::+        - 'fsoCreated'  = Creation date/time of the file
::+        - 'fsoModified' = Last Modified date/time of the file
::+        - 'fsoAccessed' = Last Accessed date/time of the file
::+
::+      Array with 2 to 7 numeric expressions (local time only)
::+        - [year,months,days,hours,minutes,seconds,milliseconds]
::+            year and months are required, the rest are optional
::+            Missing values are assumed to be 0
::+            Missing values are not allowed between specified values
::+            month 0 = January
::+            day 1 = First day of month, day 0 = Last day of prior month
::+
::+          Examples:
::+            dt:[2014,3,1,17,30,22,457]  = April 1, 2014, 17:30:22.457
::+            dt:[2014,0,1]               = January 1, 2014, 00:00:00
::+            dt:[2014,0]                 = December 31, 2013, 00:00:00
::+            dt:[2014,,10]               = error
::+
::+  Date/Time offsets and time zone options all use the same syntax.
::+  The value represents a numeric offset for the specified time unit.
::+  It may be expressed as a numeric expression (math allowed), or
::+  a numeric string (no math allowed). Both positive and negative
::+  values may be used.
::+
::+    oy:  Year offset
::+    om:  Months offset
::+    od:  Days offset
::+    oh:  Hours offset
::+    on:  Minutes offset
::+    os:  Seconds offset
::+    of:  Milliseconds (Fractional seconds) offset
::+
::+    olm: LocalMonths offset (adjusted for changes in daylight savings)
::+    old: LocalDays offset (adjusted for changes in daylight savings)
::+
::+    tz:  Time zone used for output = minutes offset from UTC
::+
::+  The fmt: option is a string that specifies the format of the output.
::+  Strings within curly braces are replaced by dynamic components that are
::+  derived from the computed time stamp. Strings within braces that do not
::+  match a fmt: component are left as is. Strings not in braces are left
::+  as is. The format component names are not case senstive.
::+
::+  For example, a U.S. date would be represented as fmt:'{yyyy}/{mm}/{dd}'
::+
::+  The default format is '{ISOTS}', which yields YYYYMMDDThhmmss.fff+hhmm
::+
::+    {YYYY}  4 digit year, zero padded
::+
::+    {YY}    2 digit year, zero padded
::+
::+    {Y}     year without zero padding
::+
::+    {MONTH} month name
::+
::+    {MTH}   month abbreviation
::+
::+    {MM}    2 digit month, zero padded
::+
::+    {M}     month without zero padding
::+
::+    {WEEKDAY} day of week name
::+
::+    {WKD}   day of week abbreviation
::+
::+    {W}     day of week number, 0=Sunday
::+
::+    {DD}    2 digit day, zero padded
::+
::+    {D}     day without zero padding
::+
::+    {HH}    2 digit hours, 24 hour format, zero padded
::+
::+    {H}     hours, 24 hour format without zero padding
::+
::+    {HH12}  2 digit hours, 12 hour format, zero padded
::+
::+    {H12}   hours, 12 hour format without zero padding
::+
::+    {NN}    2 digit minutes, zero padded
::+
::+    {N}     minutes without padding
::+
::+    {SS}    2 digit seconds, zero padded
::+
::+    {S}     seconds without padding
::+
::+    {FFF}   3 digit milliseconds, zero padded
::+
::+    {F}     milliseconds without padding
::+
::+    {AM}    AM or PM in upper case
::+
::+    {PM}    am or pm in lower case
::+
::+    {ZZZZ}  timezone expressed as minutes offset from UTC,
::+            zero padded to 3 digits with sign
::+
::+    {Z}     timzone minutes offset from UTC without padding
::+
::+    {ZS}    timezone sign
::+
::+    {ZH}    timezone hours hours offset from UTC, (no sign),
::+            padded to 2 digits
::+
::+    {ZM}    timezone minutes offset from UTC (no sign),
::+            padded to 2 digits
::+
::+    {ISOTS} YYYYMMDDThhmmss.fff+hhss
::+            Compressed ISO 8601 date/time (timestamp) with milliseconds
::+            and time zone
::+
::+    {ISODT} YYYYMMDD
::+            Compressed ISO 8601 date format
::+
::+    {ISOTM} hhmmss.fff
::+            Compressed ISO 8601 time format with milliseconds
::+
::+    {ISOTZ} +hhmm
::+            Compressed ISO 8601 timezone format
::+
::+    {ISO-TS} YYYY-MM-DDThh:mm:ss.fff+hh:ss
::+             ISO 8601 date/time (timestamp) with milliseconds and time zone
::+
::+    {ISO-DT} YYYY-MM-DD
::+             ISO 8601 date format
::+
::+    {ISO-TM} hh:mm:ss.fff
::+             ISO 8601 time format with milliseconds
::+
::+    {ISO-TZ} +hh:mm
::+             ISO 8601 timezone
::+
::+    {U}     Unix Epoch time: same as {US}
::+            Seconds since 1970-01-01 00:00:00 UTC.
::+            Negative numbers represent dates prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UMS}   Milliseconds since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {US}    Seconds since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UM}    Minutes since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UH}    Hours since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UD}    Days since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {USD}   Decimal seconds since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UMD}   Decimal minutes since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UHD}   Decimal hours since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {UDD}   Decimal days since 1970-01-01 00:00:00.000 UTC.
::+            Negative numbers represent days prior to 1970-01-01.
::+            This value should not be used with the -TZ option
::+
::+    {{}     A { character
::+
::+  The following options override the default English names for the months
::+  and days of the week. The value for each option is a space delimited list
::+  of names or abbreviations.
::+
::+    wkd: Day of week abbreviations
::+         default = 'Sun Mon Tue Wed Thu Fri Sat'
::+
::+    weekday: Day of week names
::+         default = 'Sunday Monday Tuesday Wednesday Thursday Friday'
::+
::+    mth: Month abbreviations
::+         default = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'
::+
::+    month: Month names
::+         default = 'January February March April May Jun July August'
::+                 + ' September October November December'
::+
============= :Batch portion ============
@echo off
setlocal disableDelayedExpansion

if .%2 equ . (
  set "test=%~1"
  setlocal enableDelayedExpansion
  if "!test:~0,1!" equ "-" set "test=/!test:~1!"
  if "!test!" equ "/?" (
    for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
    exit /b 0
  ) else if "!test!" equ "/??" 2>nul (
    (for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A)|more /e
    exit /b 0
  ) else if /i "!test!" equ "/?ts()" (
    for /f "tokens=* delims=:+" %%A in ('findstr "^::+" "%~f0"') do @echo(%%A
    exit /b 0
  ) else if /i "!test!" equ "/??ts()" 2>nul (
    (for /f "tokens=* delims=:+" %%A in ('findstr "^::+" "%~f0"') do @echo(%%A)|more /e
    exit /b 0
  ) else if /i "!test!" equ "/?regex" (
    explorer "http://msdn.microsoft.com/en-us/library/ae5bf541(v=vs.80).aspx"
    exit /b 0
  ) else if /i "!test!" equ "/?replace" (
    explorer "http://msdn.microsoft.com/en-US/library/efy6s3e6(v=vs.80).aspx"
    exit /b 0
  ) else if /i "!test!" equ "/?version" (
    for /f "tokens=* delims=:" %%A in ('findstr "^::JREN\.BAT" "%~f0"') do @echo(%%A
    exit /b 0
  ) else (
    call :err "Insufficient arguments"
    exit /b 2
  )
)

:: Define options
set "options= /D: /FM:"" /FX:"" /I: /J: /L: /LIST: /NBEG:1 /NINC:1 /NPAD:3 /P:. /PM:"" /PX:"" /RFM:"" /RFX:"" /RPM:"" /RPX:"" /Q: /S: /T: /U: "

:: Set default option values
for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"

:: Get options
:loop
if not "%~3"=="" (
  set "test=%~3"
  setlocal enableDelayedExpansion
  if "!test:~0,1!" equ "-" set "test=/!test:~1!"
  if "!test:~0,1!" neq "/" (
    call :err "Too many arguments"
    exit /b 2
  )
  for /f "delims=" %%A in ("!test!") do (
    set "test=!options:*%%A:=! "
    if "!test!"=="!options! " (
        endlocal
        call :err "Invalid option %~3"
        exit /b 2
    ) else if "!test:~0,1!"==" " (
        endlocal
        set "%%A=1"
        if /i "%%A" equ "/L" set "/U="
        if /i "%%A" equ "/U" set "/L="
    ) else (
        endlocal
        if %4. equ . (
          call :err "Missing %~3 value"
          exit /b 2
        )
        set "%%A=%~4"
        shift /3
    )
  )
  shift /3
  goto :loop
)

:: Execute
cscript //E:JScript //nologo "%~f0" %1 %2
exit /b %errorlevel%

:err
>&2 (
  echo ERROR: %~1
)
exit /b

************* JScript portion **********/
var $n
var _g=new Object();
try {

  _g.defineReplFunc=function() {
    eval(_g.replFunc);
  }

  _g.main=function() {

    function err( msg, rtn ) {
      WScript.StdErr.WriteLine(msg);
      if (rtn) WScript.Quit(rtn);
    }

    function BuildRegex( loc, regex, options ) {
      try {
        return regex ? new RegExp( regex, options ) : false;
      } catch(e) {
        err( 'Invalid '+loc+' regular expression: '+e.message, 1);
      }
    }

    function GetInt( loc, numStr, minVal ) {
      var n = parseInt( numStr );
      if (isNaN(n) || n<minVal) {
        err( 'Error: Invalid '+loc+' value', 1 );
      }
      return n;
    }

    var env = WScript.CreateObject("WScript.Shell").Environment("Process"),
        fso = new ActiveXObject("Scripting.FileSystemObject");

    try {
      var root = fso.GetFolder( env('/P') );
    } catch(e) {
      err( 'Invalid /P path: '+e.message, 1 );
    }

    function MaskRepl($0) {
      switch ($0) {
        case '/P:':
        case '/p:': return root.Path.replace(/[.^$*+?()[{\\|]/g,"\\$&");
        case '**':  return '.*';
        case '*':   return '[^\\\\]*';
        case '?':   return '[^\\\\.]?';
        default:    return '\\'+$0;
      }
    }

    var args=WScript.Arguments,
        search = BuildRegex( 'Search', args.Item(0), env('/I')?'gi':'g' ),
        replace=args.Item(1),
        rMask = BuildRegex( '/RFM', env('/RFM'), 'i' ),
        rExclude = BuildRegex( '/RFX', env('/RFX'), 'i' ),
        rPathMask = BuildRegex( '/RPM', env('/RPM'), 'i' ),
        rPathExclude = BuildRegex( '/RPX', env('/RPX'), 'i' ),
        regex = new RegExp("/P:|[*][*]|[*]|[?]|[.^$+()[{\\\\]","ig");
        mask = env('/FM') ? new RegExp( '^(?:' + env('/FM').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        exclude = env('/FX') ? new RegExp( '^(?:' + env('/FX').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        pathMask = env('/PM') ? new RegExp( '^(?:' + env('/PM').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        pathExclude = env('/PX') ? new RegExp( '^(?:' + env('/PX').replace(regex,MaskRepl) + ')$', 'i' ) : false;
        upper = env('/U'),
        lower = env('/L'),
        recurse = env('/S'),
        jscript = env('/J'),
        list = env('/LIST'),
        beg = GetInt( '/NBEG', env('/NBEG'), 0 ),
        inc = GetInt( '/NINC', env('/NINC'), 1 ),
        pad = GetInt( '/NPAD', env('/NPAD'), 1 ),
        padStr = Array( pad+1 ).join('0'),
        test = env('/T'),
        quiet = env('/Q');

    _g.dirs = env('/D');

    function ProcessFolder( folder ) {
      var i, a=[];
      var num = beg;
      if (recurse || _g.dirs) {
        var folders = new Enumerator(folder.SubFolders);
        for( i=0 ; !folders.atEnd(); folders.moveNext()) {
          a[i++]=folders.item()
          if (recurse) ProcessFolder(folders.item());
        }
      }
      if ( (!pathMask || pathMask.test(folder.Path)) &&
           (!rPathMask || rPathMask.test(folder.Path)) &&
           (!pathExclude || !pathExclude.test(folder.Path)) &&
           (!rPathExclude || !rPathExclude.test(folder.Path)) ) {
        if (!_g.dirs) {
          a=[];
          var files = new Enumerator(folder.Files);
          for (i=0; !files.atEnd(); files.moveNext()) a[i++]=files.item();
        }
        for (i=0; i<a.length; i++) {
          _g.file = a[i];
          _g.wmiFile = undefined;
          var oldName = a[i].Name;
          if ( (!mask || mask.test(oldName)) &&
               (!rMask || rMask.test(oldName)) &&
               (!exclude || !exclude.test(oldName)) &&
               (!rExclude || !rExclude.test(oldName)) ) {
            if (jscript) {
              $n = num.toString();
              if ($n.length<pad) $n = (padStr+$n).slice(-pad);
            }
            try {
              var newName = oldName.replace( search, replace );
            } catch(e) {
              err( 'Replace error: '+e.message, 1 );
            }
            if (!list) newName=newName.replace( /[ .]+$/, "" );
            if (jscript && !list && newName.search(/[<>|:/\\*?"\x00-\x1F]/)>=0) err('Error: Invalid file name character in Replace',1);
            if (upper) newName = uc(newName);
            if (lower) newName = lc(newName);
            if (newName != oldName || list) {
              try {
                var oldPath=a[i].Path;
                if (!test && !list) {
                  if (a[i].Name.toUpperCase() == newName.toUpperCase()) a[i].Name = '_{JREN_tempName}_';
                  a[i].Name = newName;
                }
                if (list) WScript.echo(newName);
                else if (!quiet) WScript.echo( '"'+oldPath+'"  -->  "'+newName+'"' );
              } catch(e) {
                if (a[i].Name != oldName) a[i].Name = oldName;
                err( 'Unable to rename "'+a[i].Path+'"  -->  "'+newName+'" : "'+e.message, 0 );
              }
              num+=inc;
            }
          }
        }
      }
    }

    if (jscript) {
      var regex=new RegExp('.|'+search,''),
          cnt;
      'x'.replace( regex, function(){cnt=arguments.length-2; return '';} );
      _g.replFunc='_g.replFunc=function($0';
      for (var i=1; i<cnt; i++) _g.replFunc+=',$'+i;
      _g.replFunc+=',$off,$src){return eval(_g.replace);}';
      _g.defineReplFunc();
      _g.replace = replace;
      replace = _g.replFunc;
    } else if (!list && replace.search(/[<>|:/\\*?"\x00-\x1F]/)>=0) err('Error: Invalid file name character in Replace',1);

    ProcessFolder( root );
    WScript.Quit(0);
  }

  _g.main();

} catch(e) {
  WScript.StdErr.WriteLine("JScript runtime error: "+e.message);
  WScript.Quit(1);
}

function lc(str) { return str.toLowerCase(); }

function uc(str) { return str.toUpperCase(); }

function lpad( val, pad ) {
  if (!pad) pad='';
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (pad+rtn).slice(-pad.length) : val;
}

function rpad( val, pad ) {
  if (!pad) pad='';
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (rtn+pad).slice(0,pad.length) : val;
}

function ts(opt) {
  if (opt===undefined) opt={};
  if (opt.constructor !== Object) badOp('ts()');
  if (!opt.wkd)     opt.wkd='Sun Mon Tue Wed Thu Fri Sat';
  if (!opt.weekday) opt.weekday='Sunday Monday Tuesday Wednesday Thursday Friday Saturday';
  if (!opt.mth)     opt.mth='Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec';
  if (!opt.month)   opt.month='January February March April May June July August September October November December';
  if (!opt.fmt)     opt.fmt='{isots}';

  var wkd     = opt.wkd.split(' '),
      weekday = opt.weekday.split(' '),
      mth     = opt.mth.split(' '),
      month   = opt.month.split(' '),
      y,m,d,w,h,h12,n,s,f,u,z,zs,za, dt,
      sp=' ', ps='/', pc=':', pd='-', pp='.', p2='00', p3='000', p4='0000';
  if (wkd.length!=7)     badOp('wkd');
  if (weekday.length!=7) badOp('weekday');
  if (mth.length!=12)    badOp('mth');
  if (month.length!=12)  badOp('month');

  dt = getDt(opt.dt);
  if (opt.oy) dt.setUTCFullYear(     dt.getUTCFullYear()    +getNum('oy'));
  if (opt.om) dt.setUTCMonth(        dt.getUTCMonth()       +getNum('om'));
  if (opt.od) dt.setUTCDate(         dt.getUTCDate()        +getNum('od'));
  if (opt.oh) dt.setUTCHours(        dt.getUTCHours()       +getNum('oh'));
  if (opt.on) dt.setUTCMinutes(      dt.getUTCMinutes()     +getNum('on'));
  if (opt.os) dt.setUTCSeconds(      dt.getUTCSeconds()     +getNum('os'));
  if (opt.of) dt.setUTCMilliseconds( dt.getUTCMilliseconds()+getNum('of'));
  if (opt.tz!==undefined) dt.setUTCMinutes(      dt.getUTCMinutes()  +(z=getNum('tz')));

  if (opt.olm) dt.setMonth(        dt.getMonth()       +getNum('olm'));
  if (opt.old) dt.setDate(         dt.getDate()        +getNum('old'));

  y = opt.tz!==undefined ? dt.getUTCFullYear(): dt.getFullYear();
  m = opt.tz!==undefined ? dt.getUTCMonth()   : dt.getMonth();
  d = opt.tz!==undefined ? dt.getUTCDate()    : dt.getDate();
  w = opt.tz!==undefined ? dt.getUTCDay()     : dt.getDay();
  h = opt.tz!==undefined ? dt.getUTCHours()   : dt.getHours();
  n = opt.tz!==undefined ? dt.getUTCMinutes() : dt.getMinutes();
  s = opt.tz!==undefined ? dt.getUTCSeconds() : dt.getSeconds();
  f = opt.tz!==undefined ? dt.getUTCMilliseconds() : dt.getMilliseconds();
  u = dt.getTime();

  h12 = h%12;
  if (!h12) h12=12;

  if (opt.tz==undefined) z=-dt.getTimezoneOffset();
  zs = z<0 ? '-' : '+';
  za = Math.abs(z);

  return opt.fmt.replace( /\{(.*?)\}/gi, repl );

  function getNum( v ) {
    var rtn = Number(opt[v]);
    if (isNaN(rtn-rtn)) badOp(v);
    return rtn;
  }

  function getDt( v ) {
    var dt, n;
    if (v===undefined) {
      dt = new Date();
    } else switch (v.constructor) {
      case Date:
      case Number:
        dt = new Date(v);
        break;
      case Array:
        try {dt=eval( 'new Date('+v.join(',')+')' )} catch(e){}
        break;
      case String:
        switch (v.toLowerCase()) {
          case '':         dt = new Date(); break;
          case 'created':  dt = getWmiDt('c'); break;
          case 'modified': dt = getWmiDt('m'); break;
          case 'accessed': dt = getWmiDt('a'); break;
          case 'fsocreated':  dt = new Date(_g.file.DateCreated); break;
          case 'fsomodified': dt = new Date(_g.file.DateLastModified); break;
          case 'fsoaccessed': dt = new Date(_g.file.DateLastAccessed); break;
          default:
            n=Number(v);
            if (isNaN(n-n)) dt = new Date(v);
            else            dt = new Date(n);
            break;
        }
        break;
    }
    if (isNaN(dt)) badOp('dt');
    return dt;
  }
 
  function badOp(option) {
    throw new Error('Invalid '+option+' value');
  }

  function trunc( n ) { return Math[n>0?"floor":"ceil"](n); }
 
  function repl($0,$1) {
    switch ($1.toUpperCase()) {
      case 'YYYY' : return lpad(y,p4);
      case 'YY'   : return (p2+y.toString()).slice(-2);
      case 'Y'    : return y.toString();
      case 'MM'   : return lpad(m+1,p2);
      case 'M'    : return (m+1).toString();
      case 'DD'   : return lpad(d,p2);
      case 'D'    : return d.toString();
      case 'W'    : return w.toString();
      case 'HH'   : return lpad(h,p2);
      case 'H'    : return h.toString();
      case 'HH12' : return lpad(h12,p2);
      case 'H12'  : return h12.toString();
      case 'NN'   : return lpad(n,p2);
      case 'N'    : return n.toString();
      case 'SS'   : return lpad(s,p2);
      case 'S'    : return s.toString();
      case 'FFF'  : return lpad(f,p3);
      case 'F'    : return f.toString();
      case 'AM'   : return h>=12 ? 'PM' : 'AM';
      case 'PM'   : return h>=12 ? 'pm' : 'am';
      case 'UMS'  : return u.toString();
      case 'USD'  : return (u/1000).toString();
      case 'UMD'  : return (u/1000/60).toString();
      case 'UHD'  : return (u/1000/60/60).toString();
      case 'UDD'  : return (u/1000/60/60/24).toString();
      case 'U'    : return trunc(u/1000).toString();
      case 'US'   : return trunc(u/1000).toString();
      case 'UM'   : return trunc(u/1000/60).toString();
      case 'UH'   : return trunc(u/1000/60/60).toString();
      case 'UD'   : return trunc(u/1000/60/60/24).toString();
      case 'ZZZZ' : return zs+lpad(za,p3);
      case 'Z'    : return z.toString();
      case 'ZS'   : return zs;
      case 'ZH'   : return lpad(trunc(za/60),p2);
      case 'ZM'   : return lpad(za%60,p2);
      case 'ISOTS'  : return ''+lpad(y,p4)+lpad(m+1,p2)+lpad(d,p2)+'T'+lpad(h,p2)+lpad(n,p2)+lpad(s,p2)+pp+lpad(f,p3)+zs+lpad(trunc(za/60),p2)+lpad(za%60,p2);
      case 'ISODT'  : return ''+lpad(y,p4)+lpad(m+1,p2)+lpad(d,p2);
      case 'ISOTM'  : return ''+lpad(h,p2)+lpad(n,p2)+lpad(s,p2)+pp+lpad(f,p3);
      case 'ISOTZ'  : return ''+zs+lpad(trunc(za/60),p2)+lpad(za%60,p2);
      case 'ISO-TS' : return ''+lpad(y,p4)+pd+lpad(m+1,p2)+pd+lpad(d,p2)+'T'+lpad(h,p2)+pc+lpad(n,p2)+pc+lpad(s,p2)+pp+lpad(f,p3)+zs+lpad(trunc(za/60),p2)+pc+lpad(za%60,p2);
      case 'ISO-DT' : return ''+lpad(y,p4)+pd+lpad(m+1,p2)+pd+lpad(d,p2);
      case 'ISO-TM' : return ''+lpad(h,p2)+pc+lpad(n,p2)+pc+lpad(s,p2)+pp+lpad(f,p3);
      case 'ISO-TZ' : return ''+zs+lpad(trunc(za/60),p2)+pc+lpad(za%60,p2);
      case 'WEEKDAY': return weekday[w];
      case 'WKD'    : return wkd[w];
      case 'MONTH'  : return month[m];
      case 'MTH'    : return mth[m];
      case '{'      : return $1;
      default       : return $0;
    }
  }

  function getWmiDt( prop ) {
    if (_g.wmi===undefined) {
      var svcLoc = new ActiveXObject("WbemScripting.SWbemLocator");
      _g.wmi = svcLoc.ConnectServer(".", "root\\cimv2");
    }
    if (_g.wmiFile===undefined) {
      _g.wmiFile = new Enumerator(_g.wmi.ExecQuery(
                                    "Select * From "+(_g.dirs?"Win32_Directory":"Cim_DataFile")
                                     +" Where Name = '"+_g.file.Path.replace(/\\/g,"\\\\").replace(/'/g,"\\'")+"'"
                                  )).item();
    }
    var wmiDt;
    switch (prop.toLowerCase()) {
      case 'c': wmiDt=_g.wmiFile.CreationDate; break;
      case 'm': wmiDt=_g.wmiFile.LastModified; break;
      case 'a': wmiDt=_g.wmiFile.LastAccessed; break;
      default: return undefined;
    }
    var tz=Number(wmiDt.substr(22));
    var dt = new Date(     wmiDt.substr(0,4)+ps+wmiDt.substr(4,2)+ps+wmiDt.substr(6,2)
                       +sp+wmiDt.substr(8,2)+pc+wmiDt.substr(10,2)+pc+wmiDt.substr(12,2)
                       +sp+wmiDt.substr(21,1)+lpad(trunc(tz/60),p2)+lpad(tz%60,p2)
                     );
    dt.setMilliseconds(Number(wmiDt.substr(15,3)));
    return dt;
  }

}

function attr(off) {
  var a=_g.file.Attributes;
  var o=off?off.substr(0,1):'';
  return  (a&1   ?'R':o?o:'r')  //Read only
         +(a&2   ?'H':o?o:'h')  //Hidden
         +(a&4   ?'S':o?o:'s')  //System
         +(a&32  ?'A':o?o:'a')  //Archive
         +(a&1024?'L':o?o:'l')  //Link or Shortcut
         +(a&2048?'C':o?o:'c'); //Compressed
}

function size(pad) {return lpad(_g.file.Size,pad);}

function type(pad) {return rpad(_g.file.Type,pad);}

function path(pad) {return rpad(_g.file.Path,pad);}

function parent(pad) {return rpad(_g.file.ParentFolder.Path,pad);}

function name(pad) {return rpad(_g.file.Name,pad);}

function sPath(pad) {return rpad(_g.file.ShortPath,pad);}

function sParent(pad) {return rpad(_g.file.ParentFolder.ShortPath,pad);}

function sName(pad) {return rpad(_g.file.ShortName,pad);}


Dave Benham

Squashman
Expert
Posts: 4486
Joined: 23 Dec 2011 13:59

Re: JREN.BAT - Rename files/folders using regular expressions

#25 Post by Squashman » 26 Oct 2016 10:07

dbenham wrote:Version 2.7 fixes a ts() bug - it failed to return UTC time when setting time zone to 0 (tz:0)

As punishment you get a time out! :D :lol:

crazyroot
Posts: 13
Joined: 14 Dec 2016 19:44

Re: JREN.BAT - Rename files/folders using regular expressions

#26 Post by crazyroot » 14 Dec 2016 19:53

Hi Dave,

I like very much JREN, thank you for this beautiful work !

Could you please explain me if it's possible to get simply $n (incrementing number for use in the name) not to be reset for each directory ?
I'm not very used to DOS' for loop so I didn't find a quick solution in case you have one ?
Thank you !!!
Cheers

EDIT : I have a bonus question as I noticed that jren with $n searches regular expression not ordered by name like 'dir /on ' for instance, so that the $n incrmenting doesn't follow alphabetical order : is there an argument or a quick solution to force alphabetical order for $n incrementing ?

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: JREN.BAT - Rename files/folders using regular expressions

#27 Post by dbenham » 15 Dec 2016 22:13

Well that was an easy mod :D

There was no way to prevent version 2.6 from resetting $n for each folder. So I added two simple lines of code, and modified two lines, (not including documentation), and now there is the new /NKEEP option that prevents $n from resetting. But beware - child folders are processed before parents. So the /NBEG value will appear in a child folder, not the root.

So here is JREN.BAT version 2.7:
jren2.7.zip
(10.58 KiB) Downloaded 1039 times


Dave Benham

crazyroot
Posts: 13
Joined: 14 Dec 2016 19:44

Re: JREN.BAT - Rename files/folders using regular expressions

#28 Post by crazyroot » 16 Dec 2016 02:38

Thank you Dave, /nkeep is really great !!

I'm still struggling with the sorting (directories+files) to get songs in order, like I explained it the topic :
http://www.dostips.com/forum/viewtopic.php?f=3&t=7617

jren is really fantastic, dream becomes reality ;)

bangaio64
Posts: 2
Joined: 23 Nov 2016 16:38

Re: JREN.BAT - Rename files/folders using regular expressions

#29 Post by bangaio64 » 01 Feb 2017 03:17

Suppose I have files with the following naming structure,
%username%_YYYY-MM-DD_%uniqueID%.txt

And I want to rename it to
%username%_YYYY.MM-%sequentialnumbering%
The sequential numbering would be specific for each month.

For example,
user1_2017-01-15_1426786413773592719.txt to user1_2017.01-01.txt
user1_2017-01-28_1426786414779595637.txt to user1_2017.01-02.txt
user1_2017-02-01_1426786413779592215.txt to user1_2017.02-01.txt
user1_2017-02-01_1426786414779592719.txt to user1_2017.02-02.txt
and so on.

I got this but of course the numbering isn't reset with each month:
jren "^(user1_)([0-9]{4})-(.*)-.*(\.txt)$" "$1+$2+'.'+lpad($3,'00')+'-'+$n+$4" /J /T /NBEG 1 /NPAD 2

Is this possible to do natively with jren?

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: JREN.BAT - Rename files/folders using regular expressions

#30 Post by dbenham » 01 Feb 2017 07:59

Not with a single JREN call.

I believe this is a bit trickier than you realize. I think you also want to reset $n every time the user and/or year changes. I can give you a relatively simple solution if you use my JREPL.BAT utility instead. It is a general purpose regular expression text processor that gives greater flexibility with added custom JScript logic.

I'm assuming the month and day values are already zero padded in your original names, and that the uniqueIDs are strictly numeric. Adjust the regex if my assumptions are wrong.

This code is untested, so I put an ECHO before the rename command. Remove ECHO once everything appears to be correct.

Code: Select all

@echo off
setlocal
set "find=^(.*_\d{4}-\d\d-)\d\d_\d+\.txt$"
set "repl='$txt=$0+'|'+$1+lpad(($1.toLowerCase()==prev?(n+=1):(n=1)),'00'); $prev=$1.toLowerCase()"
set "beg=var prev='', n=0"
for /f "delims=| tokens=1,2" %%A in (
  'dir /b /a-d /on *_????-??-??-*.txt ^| jrepl find repl /v /jmatchq /i /jbeg beg'
) do echo ren "%%A" "%%B.txt"


Dave Benham

Post Reply