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)

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

#1 Post by dbenham » 30 Nov 2014 20:22

This is the current version of JREN.BAT. See the subsequent posts in this thread for examples of usage and to follow the development history.

JREN.BAT

Code: Select all

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

::JREN.BAT version 2.8 by Dave Benham
::
::  Release History:
::    2.8  2018-10-17: Updated links to Microsoft regex and replace docs
::                     Bug fix in ts() - tz:0 was not working
::    2.7  2016-12-15: Added /NKEEP option.
::    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. The value will be reset for each
:::            folder unless the /NKEEP option is given. 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.
:::
:::      /NKEEP - Do not reset $n with each folder. Note that child folders
:::            are processed before parents, so a child folder will likely
:::            receive the initial /NBEG value.
:::
:::      /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 "https://msdn.microsoft.com/en-us/library/ae5bf541.aspx"
    exit /b 0
  ) else if /i "!test!" equ "/?replace" (
    explorer "https://msdn.microsoft.com/en-US/library/efy6s3e6.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 /NKEEP: /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 ),
        keep = env('/NKEEP'),
        padStr = Array( pad+1 ).join('0'),
        test = env('/T'),
        quiet = env('/Q');

    var num = beg;

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

    function ProcessFolder( folder ) {
      var i, a=[];
      if (!keep) eval('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);}
Last edited by dbenham on 10 Jul 2016 12:53, edited 9 times in total.

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

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

#2 Post by dbenham » 30 Nov 2014 20:34

JREN.BAT is a hybrid JScript/batch utility that renames files or folders by performing a regular expression search and replace on the names. The utility is pure script that will run natively on any Windows machine from XP forward.

Use JREN /? to get help from the command line.

Important Note: It is a good idea to initially use the /T (test) option when developing a JREN command. This will print out the rename operations that would be attempted, without actually renaming anything.

Below are a few examples of usage. Often there are multiple ways to achieve the same result.

1) Convert all .txt files to lower case in the current directory:

Using the search regular expression and the /L (lower case) and /I (ignore case) options

Code: Select all

jren ".*\.txt$" "$&" /l /i

Using the search regular expression and the /J (JScript expression) and /I options

Code: Select all

jren ".*\.txt$" "lc($0)" /j /i

Using the /FM (file mask) and /L options

Code: Select all

jren "" "" /l /fm "*.txt"

Using the /RFM (regular expression file mask) and /L options

Code: Select all

jren "" "" /l /rfm "\.txt$"


2) Rename all 76 ".jpg" files in the "C:\photos\Christmas 2014" directory to an increasing padded number followed by a constant string. Assume the current directory is "C:\photos". Resulting file names should look like "01_Christmas2014.jpg", "02_Christmas2014.jpg", etc.

Using the search regular expression and the /P (root Path), /NPAD ($n pad width), /J, and /I options

Code: Select all

jren ".*\.jpg$" "$n+'_Christmas2014.jpg'" /p "Christmas 2014" /npad 2 /j /i

Using the /P, /NPAD, /J, and /FM options

Code: Select all

jren "^.*" "$n+'_Christmas2014.jpg'" /p "Christmas 2014" /fm "*.jpg" /npad 2 /j

Using the /P, /NPAD, /J, and /RFM options

Code: Select all

jren "^.*" "$n+'_Christmas2014.jpg'" /p "Christmas 2014" /rfm "\.jpg$" /npad 2 /j


3) Rename folders, moving a numeric suffix to the front of the name, padded to 2 digits. Recursively apply this only to folders with "test" as a parent folder, except ignore folders under the root path of "C:\someName\test". Assume the current directory is the root path where recursion starts.

starting folder hierarchy:

Code: Select all

C:\someName\test
              proj 1
                someName 1
                  cat 1
                  dog 2
                  zebra 3
                anotherName 2
                test
                  car 1
                  ignore
                  motorcycle 11
                  train 3
              proj 2
                test
                  eel 3
                  fish 2
                  turtle 1
              test
                baseball 1
                basketball 2
                football 3

desired result (default sort order swaps positions of folders)

Code: Select all

C:\someName\test
              proj 1
                someName 1
                  cat 1
                  dog 2
                  zebra 3
                anotherName 2
                test
                  01 car
                  03 train
                  11 motorcycle
                  ignore
              proj 2
                test
                  01 turtle
                  02 fish
                  03 eel
              test
                01 baseball
                02 basketball
                03 football

Using the /D (rename Directories), /S (recurse subdirectories), /PM (path mask), /PX (path exclusion), and /J options

Code: Select all

  jren "(.*) (\d+)$" "lpad($2,'00')+' '+$1" /j /d /s /pm "**\test" /px "/p:"

Using the /D, /S, /J and /RPM (regular expression Path Mask) options

Code: Select all

  jren "(.*) (\d+)$" "lpad($2,'00')+' '+$1" /j /d /s /rpm "c:\\somename\\test.*\\test$"


4) Hack to recursively iterate files or folders using sophisticated filtering

When JREN renames a file, it lists the original full path in quotes, followed by -->, followed by the new name in quotes. For example, using the example from 3), one line of output would look like:

Code: Select all

"C:\someName\test\test\baseball 1" --> "001 baseball"

It is fairly easy to add the /T option and process the result with FOR /F to selectively iterate files or folders. Using a Search that returns the entire name, and a Replace of "", coupled with the /T option, would yield results like:

Code: Select all

"C:\someName\test\test\baseball 1" --> ""

Then you just need to add FOR /F with the weird delims syntax to set the delimiter to a quote:

Code: Select all

  @echo off
  for /f delims^=^" %%F in (
    'jren ".* \d+$" "" /t /d /s /pm "/p:**\test"
  ) do echo %%F

yields:

Code: Select all

C:\someName\test\proj 1\test\car 1
C:\someName\test\proj 1\test\motorcycle 11
C:\someName\test\proj 1\test\train 3
C:\someName\test\proj 2\test\eel 3
C:\someName\test\proj 2\test\fish 2
C:\someName\test\proj 2\test\turtle 1
C:\someName\test\test\baseball 1
C:\someName\test\test\basketball 2
C:\someName\test\test\football 3



Here is JREN.BAT version 1

Code: Select all

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
::JREN.BAT version 1.0
::
::  Release History:
::    2014-11-30 v1.0: Initial release
::
::************ Documentation ***********
:::
:::JREN  Search  Replace  [/Option  [Value]]...
:::JREN  /?[REGEX|REPLACE|VERSION]
:::
:::  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.
:::
:::      /D  - Rename Directories instead of files.
:::
:::      /I  - Ignore case when matching.
:::
:::      /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 .
:::
:::      /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 string str 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.
:::
:::      /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.
:::
:::      /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.
:::
:::      /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.
:::
:::      /Q  - Do not list the renamed files/folders (Quiet mode).
:::
:::      /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.
:::
:::      /?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 JREPL version number to stdout.
:::
:::  JREN.BAT was written by Dave Benham, and originally posted at
:::  http://www.dostips.com/forum/viewtopic.php?f=3&t=6081
:::

::************ Batch portion ***********
@echo off
setlocal disableDelayedExpansion

if .%2 equ . (
  if "%~1" equ "/?" (
    for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
    exit /b 0
  ) else if /i "%~1" equ "/?regex" (
    explorer "http://msdn.microsoft.com/en-us/library/ae5bf541(v=vs.80).aspx"
    exit /b 0
  ) else if /i "%~1" equ "/?replace" (
    explorer "http://msdn.microsoft.com/en-US/library/efy6s3e6(v=vs.80).aspx"
    exit /b 0
  ) else if /i "%~1" 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:"" /G: /I: /J: /L: /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!" neq "/" (
    call :err "Too many arguments"
    exit /b 2
  )
  set "test=!options:*%~3:=! "
  if "!test!"=="!options! " (
      endlocal
      call :err "Invalid option %~3"
      exit /b 2
  ) else if "!test:~0,1!"==" " (
      endlocal
      set "%~3=1"
      if /i "%~3" equ "/L" set "/U="
      if /i "%~3" equ "/U" set "/L="
  ) else (
      endlocal
      if %4. equ . (
        call :err "Missing %~3 value"
        exit /b 2
      )
      set "%~3=%~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('/G')?'g':'' + env('/I')?'i':'' ),
        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;
        dirs = env('/D'),
        upper = env('/U'),
        lower = env('/L'),
        recurse = env('/S'),
        jscript = env('/J'),
        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');

    function ProcessFolder( folder ) {
      var i, a=[];
      var num = beg;
      if (recurse || 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 (!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++) {
          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 );
            }
            newName=newName.replace( /[ .]+$/, "" );
            if (jscript && 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) {
              try {
                var oldPath=a[i].Path;
                if (!test) {
                  if (a[i].Name.toUpperCase() == newName.toUpperCase()) a[i].Name = '_{JREN_tempName}_'
                  a[i].Name = newName;
                }
                if (!quiet) WScript.echo( '"'+oldPath+'"  -->  "'+(test?newName:a[i].Name)+'"' );
              } catch(e) {
                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 (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 ) {
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (pad+rtn).slice(-pad.length) : val;
}

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


Dave Benham
Last edited by dbenham on 13 Dec 2014 07:49, edited 1 time in total.

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

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

#3 Post by Squashman » 30 Nov 2014 21:29

I am going to suggest to JJ Abrams that we name the new Star Wars movie: Star Wars Episode VII: Jscript Awakens. :lol:

foxidrive
Expert
Posts: 6031
Joined: 10 Feb 2012 02:20

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

#4 Post by foxidrive » 30 Nov 2014 22:31

Squashman wrote:I am going to suggest to JJ Abrams that we name the new Star Wars movie: Star Wars Episode VII: Jscript Awakens. :lol:


ROFL! :D

dbenham wrote:JREN.BAT is a hybrid JScript/batch utility that renames files or folders by performing a regular expression search and replace on the names. The utility is pure script that will run natively on any Windows machine from XP forward.


Nice Xmas present, Dave. :)

brinda
Posts: 78
Joined: 25 Apr 2012 23:51

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

#5 Post by brinda » 01 Dec 2014 07:18

dave, thanks

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

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

#6 Post by dbenham » 02 Dec 2014 16:59

Minor release:
- fix some documentation
- Allow options as -Option or /Option

JREN.BAT Version 1.1

Code: Select all

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
::JREN.BAT version 1.1
::
::  Release History:
::    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]
:::
:::  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.
:::
:::      /I  - Ignore case when matching.
:::
:::      /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 .
:::
:::      /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 string str 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.
:::
:::      /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.
:::
:::      /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.
:::
:::      /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.
:::
:::      /Q  - Do not list the renamed files/folders (Quiet mode).
:::
:::      /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.
:::
:::      /?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.
:::
:::  JREN.BAT was written by Dave Benham, and originally posted at
:::  http://www.dostips.com/forum/viewtopic.php?f=3&t=6081
:::

::************ 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 /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:"" /G: /I: /J: /L: /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('/G')?'g':'' + env('/I')?'i':'' ),
        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;
        dirs = env('/D'),
        upper = env('/U'),
        lower = env('/L'),
        recurse = env('/S'),
        jscript = env('/J'),
        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');

    function ProcessFolder( folder ) {
      var i, a=[];
      var num = beg;
      if (recurse || 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 (!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++) {
          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 );
            }
            newName=newName.replace( /[ .]+$/, "" );
            if (jscript && 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) {
              try {
                var oldPath=a[i].Path;
                if (!test) {
                  if (a[i].Name.toUpperCase() == newName.toUpperCase()) a[i].Name = '_{JREN_tempName}_'
                  a[i].Name = newName;
                }
                if (!quiet) WScript.echo( '"'+oldPath+'"  -->  "'+(test?newName:a[i].Name)+'"' );
              } catch(e) {
                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 (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 ) {
  var rtn=val.toString();
  return (rtn.length<pad.length) ? (pad+rtn).slice(-pad.length) : val;
}

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


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 expression

#7 Post by dbenham » 07 Dec 2014 17:16

Here are the new version 2 features:

1) Added ts( [ { [option:value [,option:value]...] } ] )

JScript function for use with /J option that performs date/time computations and returns a formatted timestamp string. I basically adapted the code from my getTimestamp.bat utility. Base dates can be derived from many sources:

- Parsed from the file/folder name
- Any of the file/folder timestamps: created, last modified, last accessed
- Today's date
- Any date of your choosing, with many options for input format

The ts() function has so many options, it needs its own dedicated help section :!:
There is a new help option to get ts() help:

Code: Select all

JREN /?TS()

2) Added the /LIST option

Display the Replace result only, without quotes, and without renaming anything.
Useful for producing user formatted directory like listings.

3) Added the following JScript functions for use with /LIST coupled with /J

File/folder attributes with optional left padding:

Code: Select all

size( [pad] )    - Size of file, or size of folder including subfolderrs

File/folder attributes with optional right padding:

Code: Select all

 attr( [pad] )    - List of characters representing attributes: RHSALC
 type( [pad] )    - A description of what Windows thinks the file is
 name( [pad] )    - The name
 path( [pad] )    - Full path
 parent( [pad] )  - Full path of the parent folder
 sName( [pad] )   - Short 8.3 name
 sPath( [pad] )   - Full path using short 8.3 names
 sParent( [pad] ) - Full path of the parent folder using short 8.3 names

4) Put a GOTO at the top to improve startup performance.

It took time for the batch processor to parse the extensive documentation, even though it does not get executed. The GOTO at the top skips the documentation, so startup times are noticeably faster.


Usage Examples:

Put the file creation timestamp at the beginning of all .txt file names

Code: Select all

C:\test>jren "^" "ts({dt:'created',fmt:'{isots} '})" /j /fm *.txt
"C:\test\a+b.txt"  -->  "20141130T121529.565-0500 a+b.txt"
"C:\test\jrepl.txt"  -->  "20141114T082855.961-0500 jrepl.txt"
"C:\test\lib1.txt"  -->  "20141125T195220.565-0500 lib1.txt"
"C:\test\lib2.txt"  -->  "20141125T195248.845-0500 lib2.txt"
"C:\test\new.txt"  -->  "20141119T233717.484-0500 new.txt"
"C:\test\old.txt"  -->  "20141119T233614.131-0500 old.txt"
"C:\test\out.txt"  -->  "20141108T162720.521-0500 out.txt"
"C:\test\test'it'.txt"  -->  "20141126T140119.044-0500 test'it'.txt"
"C:\test\test(it).txt"  -->  "20141126T144219.394-0500 test(it).txt"
"C:\test\test,it.txt"  -->  "20141126T135759.083-0500 test,it.txt"
"C:\test\test.txt"  -->  "20141112T084515.880-0500 test.txt"
"C:\test\test2.txt"  -->  "20141114T173346.175-0500 test2.txt"
The ISO format is great because it sorts chronologically, except for timestamps around the transition to or from daylight savings time. The timestamps always sort chronologically if you use UTC timestamps:

Code: Select all

C:\test>jren "^" "ts({dt:'created',fmt:'{isodt}T{isotm}Z ',tz:0})" /j /fm *.txt
"C:\test\a+b.txt"  -->  "20141130T171529.565Z a+b.txt"
"C:\test\jrepl.txt"  -->  "20141114T132855.961Z jrepl.txt"
"C:\test\lib1.txt"  -->  "20141126T005220.565Z lib1.txt"
"C:\test\lib2.txt"  -->  "20141126T005248.845Z lib2.txt"
"C:\test\new.txt"  -->  "20141120T043717.484Z new.txt"
"C:\test\old.txt"  -->  "20141120T043614.131Z old.txt"
"C:\test\out.txt"  -->  "20141108T212720.521Z out.txt"
"C:\test\test'it'.txt"  -->  "20141126T190119.044Z test'it'.txt"
"C:\test\test(it).txt"  -->  "20141126T194219.394Z test(it).txt"
"C:\test\test,it.txt"  -->  "20141126T185759.083Z test,it.txt"
"C:\test\test.txt"  -->  "20141112T134515.880Z test.txt"
"C:\test\test2.txt"  -->  "20141114T223346.175Z test2.txt"

Add yesterday's date to the end of each file name, immediately before the extension:

Code: Select all

C:\test>jren "(\.txt)$" "' '+ts({od:-1,fmt:'{iso-dt}'})+$1" /j /fm *.txt
"C:\test\a+b.txt"  -->  "a+b 2014-12-06.txt"
"C:\test\jrepl.txt"  -->  "jrepl 2014-12-06.txt"
"C:\test\lib1.txt"  -->  "lib1 2014-12-06.txt"
"C:\test\lib2.txt"  -->  "lib2 2014-12-06.txt"
"C:\test\new.txt"  -->  "new 2014-12-06.txt"
"C:\test\old.txt"  -->  "old 2014-12-06.txt"
"C:\test\out.txt"  -->  "out 2014-12-06.txt"
"C:\test\test'it'.txt"  -->  "test'it' 2014-12-06.txt"
"C:\test\test(it).txt"  -->  "test(it) 2014-12-06.txt"
"C:\test\test,it.txt"  -->  "test,it 2014-12-06.txt"
"C:\test\test.txt"  -->  "test 2014-12-06.txt"
"C:\test\test2.txt"  -->  "test2 2014-12-06.txt"

Taken from StackOverflow question: How to rename files, moving parts of filename and converting date format, with batch?
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: 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
"C:\test\Another Title Oct 1, 2014 - 2nd Description blah blah.pdf"  -->  "2014-10-01 - Another Title - 2nd Description blah blah.pdf"
"C:\test\Title Sep 29, 2014 - 2nd Description blah blah.pdf"  -->  "2014-09-29 - Title - 2nd Description blah blah.pdf"

Create a custom directory listing incorporating last modified timestamp with milliseconds, file attributes, and file size:

Code: Select all

C:\test>jren "^.*" "ts({dt:'modified',fmt:'{iso-dt} {iso-tm} {iso-tz}  '})+attr()+size('         ')+'  '+path()" /j /list /fm *.bat
2014-11-20 20:22:26.743 -05:00  rhsAlc    21989  C:\test\findrepl.bat
2014-11-23 12:40:10.085 -05:00  rhsAlc      230  C:\test\jfindstr.bat
2014-11-08 22:23:10.443 -05:00  rhsAlc       33  C:\test\lower.bat
2014-12-02 22:32:56.976 -05:00  rhsAlc    15461  C:\test\old.bat
2014-11-08 12:03:24.585 -05:00  rhsAlc      290  C:\test\pigLatin.bat
2014-11-29 17:12:21.173 -05:00  rhsAlc       20  C:\test\test.bat
2014-11-02 13:54:47.882 -05:00  rhsAlc     1184  C:\test\test01.bat
2014-11-29 13:15:01.108 -05:00  rhsAlc     1184  C:\test\test02.bat
2014-11-08 22:21:57.994 -05:00  rhsAlc       33  C:\test\upper.bat


----------------------------------------------------------------------------------------------------

And here is the actual code.

JREN.BAT version 2.2

Code: Select all

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

::JREN.BAT version 2.2
::
::  Release History:
::    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()]
:::
:::  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.
:::
:::      /I  - Ignore case when matching.
:::
:::      /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 .
:::
:::      /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.
:::
:::      /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.
:::
:::      /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.
:::
:::      /Q  - Do not list the renamed files/folders (Quiet mode).
:::
:::      /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.
:::
:::      /?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.
:::
:::  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 /i "!test!" equ "/?ts()" (
    for /f "tokens=* delims=:+" %%A in ('findstr "^::+" "%~f0"') do @echo(%%A
    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 && !test && !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 (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
Last edited by dbenham on 13 Dec 2014 07:29, edited 2 times in total.

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

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

#8 Post by dbenham » 09 Dec 2014 23:34

I got a private message from Brinda stating that the WMI calls to get the file timestamps was not working on Win 2000. There is also the issue that the WMI calls are quite slow.

So I added additional ts() dt: options to get FileSystemObject (FSO) timestamps instead of using WMI. This is much faster. There are two draw backs:

- FSO timestamps are only to second accracy - they do not have milliseconds.
- FSO timestamps are locale dependent - the timestamp will not parse properly in some countries.

The old WMI timestamps are accessed via dt:'created', dt:'modified', and dt:'accessed'.
The new FSO timestamps are accessed via dt:'fsoCreated', dt:'fsoModified', and dt:'fsoAccessed'.

I updated both the prior post as well as the current post at the top to version 2.1.


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 expression

#9 Post by dbenham » 10 Dec 2014 07:36

I updated version 2.1 to 2.2 to squash another bug.

At one point I had a /G option to make the Search global. I later opted to have all searches global, so I eliminated the /G option, but forgot to change my Search regex definition.

Version 2.2 fixes the bug by making all searches global.

Original line 684

Code: Select all

        search = BuildRegex( 'Search', args.Item(0), env('/G')?'g':'' + env('/I')?'i':'' ),

changed to

Code: Select all

        search = BuildRegex( 'Search', args.Item(0), env('/I')?'gi':'g' ),


Dave Benham

brinda
Posts: 78
Joined: 25 Apr 2012 23:51

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

#10 Post by brinda » 13 Dec 2014 06:44

dbenham wrote:I got a private message from Brinda stating that the WMI calls to get the file timestamps was not working on Win 2000. There is also the issue that the WMI calls are quite slow.

So I added additional ts() dt: options to get FileSystemObject (FSO) timestamps instead of using WMI. This is much faster. There are two draw backs:

- FSO timestamps are only to second accracy - they do not have milliseconds.
- FSO timestamps are locale dependent - the timestamp will not parse properly in some countries.

The old WMI timestamps are accessed via dt:'created', dt:'modified', and dt:'accessed'.
The new FSO timestamps are accessed via dt:'fsoCreated', dt:'fsoModified', and dt:'fsoAccessed'.

I updated both the prior post as well as the current post at the top to version 2.1.


Dave Benham


dave,

sorry for late reply. thanks for helping on this

on win2000

previous code

Code: Select all

D:\test>jren ".*" "ts({dt:'modified',fmt:'{iso-dt} {iso-tm} {iso-tz}  '})+attr()+size('         ')+'  '+path()" /j /list /fm *.htm
Replace error: Invalid dt value


with the ammended code

Code: Select all

D:\test>jren ".*" "ts({dt:'fsomodified',fmt:'{iso-dt} {iso-tm} {iso-tz}  '})+attr()+size('         ')+'  '+path()" /j /list /fm *.htm
2014-12-12 20:12:18.000 +08:00  rhsAlc    20684  D:\test\1.htm2014-12-12 20:12:18.000 +08:00  rhsAlc    20684  D:\test\1.htm
2014-12-12 20:08:28.000 +08:00  rhsAlc    16834  D:\test\10.htm2014-12-12 20:08:28.000 +08:00  rhsAlc    16834  D:\test\10.htm
2014-12-12 20:08:16.000 +08:00  rhsAlc    12917  D:\test\11.htm2014-12-12 20:08:16.000 +08:00  rhsAlc    12917  D:\test\11.htm
2014-12-12 20:08:06.000 +08:00  rhsAlc    15085  D:\test\12.htm2014-12-12 20:08:06.000 +08:00  rhsAlc    15085  D:\test\12.htm
2014-12-12 20:07:52.000 +08:00  rhsAlc    14955  D:\test\13.htm2014-12-12 20:07:52.000 +08:00  rhsAlc    14955  D:\test\13.htm

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

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

#11 Post by dbenham » 13 Dec 2014 07:53

Thanks brinda.

My example was wrong. The ".*" search term matches both the entire line, plus the position at the end of the line, so it prints out the information twice.

The search term should be changed to "^.*", and then it works perfectly.

The old search term used to work until version 2.2 when I implicitly added the global attribute to the regex search. A Global search was my original intent all along.

I've updated the examples throughout the thread to compensate for the corrected behavior.


Dave Benham

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

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

#12 Post by Squashman » 13 Dec 2014 09:20

Ugh. Shouldn't have to support 6 MS operating systems.

foxidrive
Expert
Posts: 6031
Joined: 10 Feb 2012 02:20

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

#13 Post by foxidrive » 27 Apr 2015 12:24

Dave, have ya got a second to think about this one?

A task is to basically take 5 filenames at a time to echo into a file, and run a task - then repeat with the next five files.

I aimed to use a padded number - purely for cosmetic reasons in the output set of filenames -
and I was wondering if JREN has the ability to do both these things?

The task is listed here:
http://ss64.org/viewtopic.php?pid=8345#p8345

I'm trying to avoid using delayed expansion and stacks of calls and lots of manipulating,
and the jscript tools are so much more robust, and easier on the code.


I looked at Findrepl and thought of using the errorlevel to calculate where the next set of filenames was to start from but the /o:s:e doesn't seem to support a calculation - and as above I'm always trying to avoid delayed expansion.

If you're reading Antonio, did I miss a way to do it with Findrepl?

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

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

#14 Post by dbenham » 27 Apr 2015 14:50

I don't see a simple solution using JREN, but it is fairly simple with JREPL. I've posted a solution at SS64.


Dave Benham

foxidrive
Expert
Posts: 6031
Joined: 10 Feb 2012 02:20

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

#15 Post by foxidrive » 29 Apr 2015 09:02

Followup to your post at ss64, Dave.

Post Reply