如何使 SHIFT 与批处理文件中的 %* 一起使用

发布于 2025-01-07 12:06:07 字数 432 浏览 1 评论 0原文

在 Windows XP 上的批处理文件中,我想使用 %* 扩展到除第一个参数之外的所有参数。
测试文件 (foo.bat< /em>):

@echo off
echo %*
shift
echo %*

调用:

C:\> foo a b c d e f

实际结果:

a b c d e f
a b c d e f

期望结果:

a b c d e f
b c d e f

我怎样才能达到期望的结果?谢谢!!

In my batch file on Windows XP, I want to use %* to expand to all parameters except the first.
Test file (foo.bat):

@echo off
echo %*
shift
echo %*

Call:

C:\> foo a b c d e f

Actual result:

a b c d e f
a b c d e f

Desired result:

a b c d e f
b c d e f

How can I achieve the desired result? Thanks!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

壹場煙雨 2025-01-14 12:06:07

如果 CMD.EXE 也能这样工作那该多好啊!不幸的是,没有一个好的语法可以满足您的要求。您能做的最好的事情就是自己解析命令行并构建一个新的参数列表。

像这样的东西可以工作。

@echo off
setlocal
echo %*
shift
set "args="
:parse
if "%~1" neq "" (
  set args=%args% %1
  shift
  goto :parse
)
if defined args set args=%args:~1%
echo(%args%

但如果参数包含 ^&>< 等特殊字符,上面的方法就会出现问题, | 被转义而不是引用。

参数处理是 Windows 批处理编程的众多薄弱环节之一。几乎每个解决方案都存在导致问题的异常。

Wouldn't it be wonderful if CMD.EXE worked that way! Unfortunately there is not a good syntax that will do what you want. The best you can do is parse the command line yourself and build a new argument list.

Something like this can work.

@echo off
setlocal
echo %*
shift
set "args="
:parse
if "%~1" neq "" (
  set args=%args% %1
  shift
  goto :parse
)
if defined args set args=%args:~1%
echo(%args%

But the above has problems if an argument contains special characters like ^, &, >, <, | that were escaped instead of quoted.

Argument handling is one of many weak aspects of Windows batch programming. For just about every solution, there exists an exception that causes problems.

无声情话 2025-01-14 12:06:07

这很简单:

setlocal ENABLEDELAYEDEXPANSION
  set "_args=%*"
  set "_args=!_args:*%1 =!"

  echo/%_args%
endlocal

与注释相同:

:: Enable use of ! operator for variables (! works as % after % has been processed)
setlocal ENABLEDELAYEDEXPANSION
  set "_args=%*"
  :: Remove %1 from %*
  set "_args=!_args:*%1 =!"
  :: The %_args% must be used here, before 'endlocal', as it is a local variable
  echo/%_args%
endlocal

示例:

lets say %* is "1 2 3 4":

setlocal ENABLEDELAYEDEXPANSION
  set "_args=%*"             --> _args=1 2 3 4
  set "_args=!_args:*%1 =!"  --> _args=2 3 4

  echo/%_args%
endlocal

备注:

  • 如果任何参数包含 ! 或 & char
  • 参数之间的任何多余空格都不会被删除
  • %_args% 必须在 endlocal 之前使用,因为它是本地的变量
  • 如果没有输入参数, %_args% 返回 * =
  • 如果仅输入 1 个参数,则不转换

That´s easy:

setlocal ENABLEDELAYEDEXPANSION
  set "_args=%*"
  set "_args=!_args:*%1 =!"

  echo/%_args%
endlocal

Same thing with comments:

:: Enable use of ! operator for variables (! works as % after % has been processed)
setlocal ENABLEDELAYEDEXPANSION
  set "_args=%*"
  :: Remove %1 from %*
  set "_args=!_args:*%1 =!"
  :: The %_args% must be used here, before 'endlocal', as it is a local variable
  echo/%_args%
endlocal

Example:

lets say %* is "1 2 3 4":

setlocal ENABLEDELAYEDEXPANSION
  set "_args=%*"             --> _args=1 2 3 4
  set "_args=!_args:*%1 =!"  --> _args=2 3 4

  echo/%_args%
endlocal

Remarks:

  • Does not work if any argument contains the ! or & char
  • Any extra spaces in between arguments will NOT be removed
  • %_args% must be used before endlocal, because it is a local variable
  • If no arguments entered, %_args% returns * =
  • Does not shift if only 1 argument entered
谁人与我共长歌 2025-01-14 12:06:07

不要认为有简单的方法可以做到这一点。您可以尝试使用以下解决方法:

@ECHO OFF
>tmp ECHO(%*
SET /P t=<tmp
SETLOCAL EnableDelayedExpansion
IF DEFINED t SET "t=!t:%1 =!"
ECHO(!t!

示例:

test.bat 1 2 3=4

输出:

2 3=4

Don't think there's a simple way to do so. You could try playing with the following workaround instead:

@ECHO OFF
>tmp ECHO(%*
SET /P t=<tmp
SETLOCAL EnableDelayedExpansion
IF DEFINED t SET "t=!t:%1 =!"
ECHO(!t!

Example:

test.bat 1 2 3=4

Output:

2 3=4
初心未许 2025-01-14 12:06:07

我最近不得不这样做并想出了这个:

setlocal EnableDelayedExpansion

rem Number of arguments to skip
set skip=1

for %%a in (%*) do (
  if not !position! lss !skip! (
    echo Argument: '%%a'
  ) else (
    set /a "position=!position!+1"
  )
)

endlocal

它使用循环来跳过 N 第一个参数。可用于为每个参数执行一些命令或构建新的参数列表:

setlocal EnableDelayedExpansion

rem Number of arguments to skip
set skip=1

for %%a in (%*) do (
  if not !position! lss !skip! (
    set args=!args! %%a
  ) else (
    set /a "position=!position!+1"
  )
)

echo %args%

endlocal

请注意,上面的代码将为新参数添加前导空格。可以像这样删除它:

I had to do this recently and came up with this:

setlocal EnableDelayedExpansion

rem Number of arguments to skip
set skip=1

for %%a in (%*) do (
  if not !position! lss !skip! (
    echo Argument: '%%a'
  ) else (
    set /a "position=!position!+1"
  )
)

endlocal

It uses loop to skip over N first arguments. Can be used to execute some command per argument or build new argument list:

setlocal EnableDelayedExpansion

rem Number of arguments to skip
set skip=1

for %%a in (%*) do (
  if not !position! lss !skip! (
    set args=!args! %%a
  ) else (
    set /a "position=!position!+1"
  )
)

echo %args%

endlocal

Please note that the code above will add leading space to the new arguments. It can be removed like this:

寂寞美少年 2025-01-14 12:06:07

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & ^ set "__STRING__=!__STRING__:$2C=,!" & set "__STRING__=!__STRING__:$3B=;!" & set "__STRING__=!__STRING__:$3D==!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_asterisk_char.bat

@echo off

rem Encode `*` character.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem CAUTION:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & ^ set "__STRING__=!__STRING__:$2C=,!" & set "__STRING__=!__STRING__:$3B=;!" & set "__STRING__=!__STRING__:$3D==!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_asterisk_char.bat

must be encoded separately BEFORE this script call! rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & setlocal ENABLEDELAYEDEXPANSION & if "!__STRING__!" == "!__STRING__:**=!" ( exit /b 0 ) else endlocal :LOOP setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=1 delims=*"eol^= %%i in (".!__STRING__!") do for /F "tokens=* delims="eol^= %%j in ("!__STRING__:**=!.") do endlocal & set "__STRING__=%%i$2A%%j" & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~1,-1!") do ^ if not "!__STRING__!" == "!__STRING__:**=!" ( endlocal & set "__STRING__=%%i" & goto LOOP ) else endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_equal_char.bat

@echo off

rem Encode `=` character.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem CAUTION:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & ^ set "__STRING__=!__STRING__:$2C=,!" & set "__STRING__=!__STRING__:$3B=;!" & set "__STRING__=!__STRING__:$3D==!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_asterisk_char.bat

@echo off

rem Encode `*` character.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem CAUTION:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

您可以在调用另一个命令之前转移参数。

最新实现: https://github.com/andry81 /contools/tree/HEAD/Scripts/Tools/std/callshift.bat

优点:

  • 可以处理几乎所有控制字符。
  • 可以调用内置命令。
  • 在调用命令之前恢复先前的 ERRORLEVEL 变量。
  • 可以跳过 %* 变量中的前 N ​​个使用的参数,包括
    额外的命令行参数。
  • 可以避免移位命令中的空格和制表符修剪
    线。
  • 可以在执行命令时使用重定向到文件来锁定呼叫
    线路呼叫。

缺点:

  • 控制字符如 &| 仍然必须在之前转义
    调用用户脚本(附带问题)。
  • 写入临时文件以按原样保存命令行并且
    cmd.exe /Q ... 可以完全抑制echo on
  • 在此脚本调用之前必须禁用延迟扩展功能:
    setlocal DISABLEDELAYEDEXPANSION,否则 ! 字符将被
    扩大了。
  • 批处理脚本命令行和可执行命令行有
    不同的编码器。
  • 如果命令行后面紧接着有制表符
    参数,每个参数必须至少包含一个空格
    字符,因为所有制表字符都会编码,这可能会结束
    加上参数串联以及错误的跳过和/或移位。

callshift.bat

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

示例(在控制台中):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

示例(在脚本中):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

字符编码器和解码器:

encode_sys_chars_bat_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & ^ set "__STRING__=!__STRING__:$2C=,!" & set "__STRING__=!__STRING__:$3B=;!" & set "__STRING__=!__STRING__:$3D==!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_asterisk_char.bat

must be encoded separately BEFORE this script call! rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & setlocal ENABLEDELAYEDEXPANSION & if "!__STRING__!" == "!__STRING__:**=!" ( exit /b 0 ) else endlocal :LOOP setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=1 delims=*"eol^= %%i in (".!__STRING__!") do for /F "tokens=* delims="eol^= %%j in ("!__STRING__:**=!.") do endlocal & set "__STRING__=%%i$2A%%j" & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~1,-1!") do ^ if not "!__STRING__!" == "!__STRING__:**=!" ( endlocal & set "__STRING__=%%i" & goto LOOP ) else endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_equal_char.bat

must be encoded separately BEFORE this script call! rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do for /F "tokens=1 delims=="eol^= %%j in (".!__STRING__!") do endlocal & set "__HEAD__=%%j" & set "__TAIL__=.%%i" & ^ setlocal ENABLEDELAYEDEXPANSION & if "!__HEAD__!" == "!__TAIL__!" ( exit /b 0 ) else endlocal set "__STRING__=" setlocal ENABLEDELAYEDEXPANSION :LOOP if "!__HEAD__!" == "!__TAIL__!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!!__TAIL__:~1!") do endlocal & endlocal & set "__STRING__=%%i" & exit /b 0 set "__OFFSET__=2" & set "__TMP__=!__HEAD__!" & for %%i in (65536 32768 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do if not "!__TMP__:~%%i,1!" == "" set /A "__OFFSET__+=%%i" & set "__TMP__=!__TMP__:~%%i!" if defined __TAIL__ set "__TAIL__=!__TAIL__:~%__OFFSET__%!" set "__STRING__=!__STRING__!!__HEAD__:~1!$3D" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do for /F "tokens=1 delims=="eol^= %%j in (".!__TAIL__!") do for /F "tokens=* delims="eol^= %%k in (".!__TAIL__!") do ^ endlocal & set "__STRING__=%%i" & set "__HEAD__=%%j" & set "__TAIL__=%%k" & setlocal ENABLEDELAYEDEXPANSION goto LOOP

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & ^ set "__STRING__=!__STRING__:$2C=,!" & set "__STRING__=!__STRING__:$3B=;!" & set "__STRING__=!__STRING__:$3D==!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_asterisk_char.bat:

@echo off

rem Encode `*` character.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem CAUTION:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & ^ set "__STRING__=!__STRING__:$2C=,!" & set "__STRING__=!__STRING__:$3B=;!" & set "__STRING__=!__STRING__:$3D==!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_asterisk_char.bat:

must be encoded separately BEFORE this script call! rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & setlocal ENABLEDELAYEDEXPANSION & if "!__STRING__!" == "!__STRING__:**=!" ( exit /b 0 ) else endlocal :LOOP setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=1 delims=*"eol^= %%i in (".!__STRING__!") do for /F "tokens=* delims="eol^= %%j in ("!__STRING__:**=!.") do endlocal & set "__STRING__=%%i$2A%%j" & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~1,-1!") do ^ if not "!__STRING__!" == "!__STRING__:**=!" ( endlocal & set "__STRING__=%%i" & goto LOOP ) else endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_equal_char.bat:

@echo off

rem Encode `=` character.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem CAUTION:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & ^ set "__STRING__=!__STRING__:$2C=,!" & set "__STRING__=!__STRING__:$3B=;!" & set "__STRING__=!__STRING__:$3D==!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_asterisk_char.bat:

@echo off

rem Encode `*` character.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem CAUTION:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

@echo off

rem Decode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line
rem  ,;=        - separator characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

@echo off

rem Encode these characters:
rem  $          - encode character
rem  |&()<>     - control flow characters
rem  '`^%!+     - escape or sequence expand characters (`+` is a unicode codepoint sequence character in 65000 code page)
rem  ?*         - globbing characters in the `for ... %%i in (...)` expression or in a command line

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call: `setlocal DISABLEDELAYEDEXPANSION`, otherwise
rem   the `!` character will be expanded.
rem

rem NOTE:
rem   Character `

You can shift arguments before call to another command.

Latest implementation: https://github.com/andry81/contools/tree/HEAD/Scripts/Tools/std/callshift.bat

Pros:

  • Can handle almost all control characters.
  • Can call builtin commands.
  • Does restore previous ERRORLEVEL variable before call a command.
  • Can skip first N used arguments from the %* variable including
    additional command line arguments.
  • Can avoid spaces and tabulation characters trim in the shifted command
    line.
  • Can lock the call using a redirection into a file while at the command
    line call.

Cons:

  • The control characters like & and | still must be escaped before
    call in a user script (side issue).
  • Does write to a temporary file to save the command line as is and
    cmd.exe /Q ... can suppress echo on at all.
  • The delayed expansion feature must be disabled before this script call:
    setlocal DISABLEDELAYEDEXPANSION, otherwise the ! character will be
    expanded.
  • A batch script command line and an executable command line has
    different encoders.
  • In case of a tabulation character immediately after a command line
    argument, you must entail each argument at least with one space
    character, because all tabulation characters does encode which may end
    up with arguments concatenation and so wrong skip and/or shift.

callshift.bat:

@echo off

rem USAGE:
rem   callshift.bat [-exe] [-notrim] [-skip <skip-num>] [-lockfile <lock-file> [-trylock] [-lock-sleep-cmdline <lock-sleep-cmdline>]] [<shift> [<command> [<cmdline>...]]]

rem Description:
rem   Script calls `<command>` with skipped and shifted `<cmdline>`.
rem   The `call` prefix is not required to call batch scripts.

rem -exe
rem   Use exe command line encoder instead of the batch as by default.
rem   An executable command line does not use `,;=` characters as command line
rem   arguments separator.

rem -notrim
rem   Avoids spaces trim in the shifted command line.

rem -skip <skip-num>
rem   Number of `<cmdline>` arguments to skip before shift.
rem   If not defined, then 0.

rem -lockfile <lock-file>
rem   Calls a command line under the lock (a file redirection trick).
rem   If the lock was holden before the call, then the call waits the unlock
rem   if `-trylock` flag is not defined. Otherwise just ignores and the script
rem   returns a negative error code (-1024).
rem   The lock file directory must exist before the call.
rem   The lock file will be removed on script exit.

rem -lockfile <lock-file>
rem   Lock file path to lock the call.

rem -trylock
rem   Try to lock and if not, then exit immediately (-1024) instead of waiting
rem   the lock.
rem   Has no effect if `-lockfile` is not defined.

rem -lock-sleep-cmdline <lock-sleep-cmdline>
rem   The command line for the `sleep.bat` script to call on before attempt to
rem   acquire another lock.
rem   Has no effect if `-lockfile` is not defined.
rem   If not defined, then `50` (ms) is used by default.

rem <shift>:
rem   Number of `<cmdline>` arguments to skip and shift.
rem   If >=0, then shifts by `<shift>` beginning from `<skip-num>` argument.
rem   If < 0, then shifts by `|<shift>|` beginning from `<skip-num>+|<shift>|`
rem   argument.

rem <command>:
rem   Command to call with arguments.

rem CAUTION:
rem   The delayed expansion feature must be disabled before this script call:
rem   `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem   expanded.
rem

rem Examples (in console):
rem   1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | "=" 3
rem   2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
rem      "1 2" ! ? * & | , ; = = "=" 3
rem   3. >callshift.bat 2 echo."1 2" 3 4 5
rem      "1 2" 5
rem   4. >callshift.bat . set | sort
rem   5. >errlvl.bat 123
rem      >callshift.bat
rem      >callshift.bat 0 echo.
rem      >callshift.bat 0 echo 1 2 3
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=123
rem   6. >callshift.bat -3 echo 1 2 3 4 5 6 7
rem      1 2 3 7
rem      rem in a script
rem      >call callshift.bat -3 command %%3 %%2 %%1 %%*
rem   7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
rem      a b 1 2 3 7
rem      rem in a script
rem      >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
rem   8. >callshift.bat 0 exit /b 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem   9. >errlvl.bat 123
rem      >callshift.bat 0 errlvl.bat 321
rem      >echo ERRORLEVEL=%ERRORLEVEL%
rem      ERRORLEVEL=321
rem  10. >callshift.bat -notrim 1 echo  a  b  c  d
rem       b  c  d
rem  11. >callshift.bat 0 echo.^>cmd param0 param1
rem      >cmd param0 param1
rem  12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

rem Examples (in script):
rem   1. set "$5E$3E=^>"
rem      call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
rem   2. set "TAB=  "
rem      call callshift.bat -notrim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

rem Pros:
rem
rem   * Can handle almost all control characters.
rem   * Can call builtin commands.
rem   * Does restore previous ERRORLEVEL variable before call a command.
rem   * Can skip first N used arguments from the `%*` variable including
rem     additional command line arguments.
rem   * Can avoid spaces and tabulation characters trim in the shifted command
rem     line.
rem   * Can lock the call using a redirection into a file while at the command
rem     line call.
rem
rem Cons:
rem
rem   * The control characters like `&` and `|` still must be escaped before
rem     call in a user script (side issue).
rem   * Does write to a temporary file to save the command line as is and
rem     `cmd.exe /Q ...` can suppress `echo on` at all.
rem   * The delayed expansion feature must be disabled before this script call:
rem     `setlocal DISABLEDELAYEDEXPANSION`, otherwise the `!` character will be
rem     expanded.
rem   * A batch script command line and an executable command line has
rem     different encoders.
rem   * In case of a tabulation character immediately after a command line
rem     argument, you must entail each argument at least with one space
rem     character, because all tabulation characters does encode which may end
rem     up with arguments concatenation and so wrong skip and/or shift.

rem with save of previous error level
setlocal DISABLEDELAYEDEXPANSION & set LAST_ERROR=%ERRORLEVEL%

rem drop last error level
call;

if defined SCRIPT_TEMP_CURRENT_DIR (
  set "CMDLINE_TEMP_FILE=%SCRIPT_TEMP_CURRENT_DIR%\%~n0.%RANDOM%-%RANDOM%.txt"
) else set "CMDLINE_TEMP_FILE=%TEMP%\%~n0.%RANDOM%-%RANDOM%.txt"

rem redirect command line into temporary file to print it correcly
(
  setlocal DISABLEEXTENSIONS
  (set PROMPT=$_)
  echo on
  for %%z in (%%z) do rem * %*#
  @echo off
  endlocal
) > "%CMDLINE_TEMP_FILE%"

for /F "usebackq tokens=* delims="eol^= %%i in ("%CMDLINE_TEMP_FILE%") do set "__STRING__=%%i"

del /F /Q /A:-D "%CMDLINE_TEMP_FILE%" >nul 2>nul

rem WORKAROUND:
rem   In case if `echo` is turned off externally.
rem
if not defined __STRING__ exit /b %LAST_ERROR%

setlocal ENABLEDELAYEDEXPANSION & if not "!__STRING__:~6!" == "# " (
  for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~6,-2!") do endlocal & set "__STRING__=%%i"
) else endlocal & set "__STRING__="

if not defined __STRING__ exit /b %LAST_ERROR%

set "?~dp0=%~dp0"

rem script flags
set FLAG_SHIFT=0
set FLAG_EXE=0
set FLAG_NO_TRIM=0
set FLAG_SKIP=0
set "FLAG_LOCK_FILE="
set "FLAG_LOCK_SLEEP_CMDLINE= 50"
set FLAG_TRYLOCK=0

rem flags always at first
set "FLAG=%~1"

if defined FLAG ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if defined FLAG if "%FLAG%" == "-exe" (
  set FLAG_EXE=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-notrim" (
  set FLAG_NO_TRIM=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

if defined FLAG if "%FLAG%" == "-skip" (
  set "FLAG_SKIP=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lockfile" (
  set "FLAG_LOCK_FILE=%~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-lock-sleep-cmdline" (
  set "FLAG_LOCK_SLEEP_CMDLINE= %~2"
  shift
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=2
)

if defined FLAG if "%FLAG%" == "-trylock" (
  set FLAG_TRYLOCK=1
  shift
  call set "FLAG=%%~1"
  set /A FLAG_SHIFT+=1
)

set "SHIFT=%~1"
set "COMMAND="
set "CMDLINE="

rem test on invalid flag
if not defined SHIFT exit /b %LAST_ERROR%

set "SHIFT_=%SHIFT%"

rem cast to integer
set /A SHIFT+=0

if not "%SHIFT%" == "%SHIFT_%" exit /b %LAST_ERROR%

if not defined FLAG_LOCK_FILE goto SKIP_CALL_LOCK

for /F "tokens=* delims="eol^= %%i in ("%FLAG_LOCK_FILE%\.") do set "FLAG_LOCK_FILE_DIR=%%~dpi"

if not exist "%FLAG_LOCK_FILE_DIR%*" (
  echo.%~nx0: error: lock file directory does not exist: "%FLAG_LOCK_FILE_DIR%"
  exit /b -1024
) >&2

rem lock loop
:CALL_LOCK_LOOP

rem lock via redirection to file
set LOCK_ACQUIRE=0
( ( set "LOCK_ACQUIRE=1" & call :LOCKED_CALL ) 9> "%FLAG_LOCK_FILE%" ) 2>nul

set LAST_ERROR=%ERRORLEVEL%

if %LOCK_ACQUIRE% EQU 0 (
  if %FLAG_TRYLOCK% NEQ 0 (
    del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul
    exit /b -1024
  )

  call "%%?~dp0%%sleep.bat"%%FLAG_LOCK_SLEEP_CMDLINE%%

  goto CALL_LOCK_LOOP
)

del /F /Q /A:-D "%FLAG_LOCK_FILE%" >nul 2>nul

exit /b %LAST_ERROR%

:SKIP_CALL_LOCK
:LOCKED_CALL

rem cast to integer
set /A FLAG_SKIP+=0

set /A COMMAND_INDEX=FLAG_SHIFT+1
set /A ARG0_INDEX=FLAG_SHIFT+2

set /A SKIP=FLAG_SHIFT+2+FLAG_SKIP

if %SHIFT% GEQ 0 (
  set /A SHIFT+=SKIP
) else (
  set /A SKIP+=-SHIFT
  set /A SHIFT=FLAG_SHIFT+2+FLAG_SKIP-SHIFT*2
)

rem encode specific command line characters
if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\encode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\encode_sys_chars_exe_cmdline.bat"

rem CAUTION:
rem   Encodes ALL tabulation characters.
rem
if %FLAG_NO_TRIM% NEQ 0 setlocal ENABLEDELAYEDEXPANSION & ^
set "__STRING__=!__STRING__:  = $20!" & set "__STRING__=!__STRING__:$20 =$20$20!" & ^
set "__STRING__=!__STRING__:        =   $09!" & set "__STRING__=!__STRING__:$09 =$09$09!" & ^
set "__STRING__=!__STRING__:     =$09$20!" & set "__STRING__=!__STRING__:$09 =$09$20!" & ^
for /F "tokens=* delims="eol^= %%i in ("!__STRING__:    =$09!") do endlocal & set "__STRING__=%%i"

set INDEX=0

setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & for %%j in (%%i) do (
  setlocal ENABLEDELAYEDEXPANSION & if !INDEX! GEQ !ARG0_INDEX! (
    if !INDEX! LSS !SKIP! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else if !INDEX! GEQ !SHIFT! (
      if defined CMDLINE (
        for /F "tokens=* delims="eol^= %%v in ("!CMDLINE!") do endlocal & set "CMDLINE=%%v %%j"
      ) else endlocal & set "CMDLINE=%%j"
    ) else endlocal
  ) else if !INDEX! EQU !COMMAND_INDEX! (
    endlocal & set "COMMAND=%%j"
  ) else endlocal
  set /A INDEX+=1
)

if not defined COMMAND endlocal & exit /b %LAST_ERROR%
setlocal ENABLEDELAYEDEXPANSION & if defined CMDLINE (
  set "__STRING__=!COMMAND! !CMDLINE!"
) else set "__STRING__=!COMMAND!"
if %FLAG_NO_TRIM% NEQ 0 set "__STRING__=!__STRING__:$20= !" & set "__STRING__=!__STRING__:$09=  !"

setlocal DISABLEDELAYEDEXPANSION & if %FLAG_EXE% EQU 0 (
  call "%%?~dp0%%encode\decode_sys_chars_bat_cmdline.bat"
) else call "%%?~dp0%%encode\decode_sys_chars_exe_cmdline.bat"
setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%v in ("!__STRING__!") do endlocal & endlocal & endlocal & endlocal & call :SETERRORLEVEL %LAST_ERROR% & (
  %%v
)
exit /b

:SETERRORLEVEL
exit /b %*

Examples (in console):

  1. >callshift.bat 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | "=" 3
  2. >callshift.bat -exe 0 echo "1 2" ! ? * ^& ^| , ; = ^= "=" 3
     "1 2" ! ? * & | , ; = = "=" 3
  3. >callshift.bat 2 echo."1 2" 3 4 5
     "1 2" 5
  4. >callshift.bat . set | sort
  5. >errlvl.bat 123
     >callshift.bat
     >callshift.bat 0 echo.
     >callshift.bat 0 echo 1 2 3
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=123
  6. >callshift.bat -3 echo 1 2 3 4 5 6 7
     1 2 3 7
     rem in a script
     >call callshift.bat -3 command %%3 %%2 %%1 %%*
  7. >callshift.bat -skip 2 -3 echo a b 1 2 3 4 5 6 7
     a b 1 2 3 7
     rem in a script
     >call callshift.bat -skip 2 -3 command param0 param1 %%3 %%2 %%1 %%*
  8. >callshift.bat 0 exit /b 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
  9. >errlvl.bat 123
     >callshift.bat 0 errlvl.bat 321
     >echo ERRORLEVEL=%ERRORLEVEL%
     ERRORLEVEL=321
 10. >callshift.bat -no_trim 1 echo  a  b  c  d
      b  c  d
 11. >callshift.bat 0 echo.^>cmd param0 param1
     >cmd param0 param1
 12. >callshift.bat -lockfile "%TEMP%\lock0.myscript" 0 echo.Exclusive print

Examples (in script):

  1. set "$5E$3E=^>"
     call callshift.bat 0 echo.%%$5E$3E%%cmd param0 param1
  2. set "TAB=  "
     call callshift.bat -no_trim 0 echo.cmd %%TAB%% %%TAB%% param0  %%TAB%%%%TAB%%  %%TAB%%%%TAB%%  param1 %%TAB%% %%TAB%%param2 %%TAB%%param3

Character encoders and decoders:

encode_sys_chars_bat_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

encode_sys_chars_exe_cmdline.bat:

already does encode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:$=$24!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:!=$21%" call "%%~dp0encode_asterisk_char.bat" & call "%%~dp0encode_equal_char.bat" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:|=$7C!" & set "__STRING__=!__STRING__:&=$26!" & set "__STRING__=!__STRING__:(=$28!" & set "__STRING__=!__STRING__:)=$29!" & ^ set "__STRING__=!__STRING__:<=$3C!" & set "__STRING__=!__STRING__:>=$3E!" & set "__STRING__=!__STRING__:'=$27!" & set "__STRING__=!__STRING__:`=$60!" & ^ set "__STRING__=!__STRING__:^=$5E!" & set "__STRING__=!__STRING__:%%=$25!" & set "__STRING__=!__STRING__:+=$2B!" & ^ set "__STRING__=!__STRING__:?=$3F!" & set "__STRING__=!__STRING__:,=$2C!" & set "__STRING__=!__STRING__:;=$3B!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_bat_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

decode_sys_chars_exe_cmdline.bat:

already does decode. rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:"=$22!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" set "__STRING__=%__STRING__:$21=!%" setlocal ENABLEDELAYEDEXPANSION & set "__STRING__=!__STRING__:$22="!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & set "__STRING__=%%i" setlocal ENABLEDELAYEDEXPANSION & ^ set "__STRING__=!__STRING__:$7C=|!" & set "__STRING__=!__STRING__:$26=&!" & set "__STRING__=!__STRING__:$28=(!" & set "__STRING__=!__STRING__:$29=)!" & ^ set "__STRING__=!__STRING__:$3C=<!" & set "__STRING__=!__STRING__:$3E=>!" & set "__STRING__=!__STRING__:$27='!" & set "__STRING__=!__STRING__:$60=`!" & ^ set "__STRING__=!__STRING__:$5E=^!" & set "__STRING__=!__STRING__:$25=%%!" & set "__STRING__=!__STRING__:$2B=+!" & ^ set "__STRING__=!__STRING__:$3F=?!" & set "__STRING__=!__STRING__:$2A=*!" & ^ set "__STRING__=!__STRING__:$2C=,!" & set "__STRING__=!__STRING__:$3B=;!" & set "__STRING__=!__STRING__:$3D==!" & set "__STRING__=!__STRING__:$24=$!" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_asterisk_char.bat:

must be encoded separately BEFORE this script call! rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & setlocal ENABLEDELAYEDEXPANSION & if "!__STRING__!" == "!__STRING__:**=!" ( exit /b 0 ) else endlocal :LOOP setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=1 delims=*"eol^= %%i in (".!__STRING__!") do for /F "tokens=* delims="eol^= %%j in ("!__STRING__:**=!.") do endlocal & set "__STRING__=%%i$2A%%j" & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__:~1,-1!") do ^ if not "!__STRING__!" == "!__STRING__:**=!" ( endlocal & set "__STRING__=%%i" & goto LOOP ) else endlocal & endlocal & set "__STRING__=%%i" exit /b 0

encode_equal_char.bat:

must be encoded separately BEFORE this script call! rem if not defined __STRING__ exit /b 0 setlocal DISABLEDELAYEDEXPANSION & ^ setlocal ENABLEDELAYEDEXPANSION & for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do for /F "tokens=1 delims=="eol^= %%j in (".!__STRING__!") do endlocal & set "__HEAD__=%%j" & set "__TAIL__=.%%i" & ^ setlocal ENABLEDELAYEDEXPANSION & if "!__HEAD__!" == "!__TAIL__!" ( exit /b 0 ) else endlocal set "__STRING__=" setlocal ENABLEDELAYEDEXPANSION :LOOP if "!__HEAD__!" == "!__TAIL__!" for /F "tokens=* delims="eol^= %%i in ("!__STRING__!!__TAIL__:~1!") do endlocal & endlocal & set "__STRING__=%%i" & exit /b 0 set "__OFFSET__=2" & set "__TMP__=!__HEAD__!" & for %%i in (65536 32768 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do if not "!__TMP__:~%%i,1!" == "" set /A "__OFFSET__+=%%i" & set "__TMP__=!__TMP__:~%%i!" if defined __TAIL__ set "__TAIL__=!__TAIL__:~%__OFFSET__%!" set "__STRING__=!__STRING__!!__HEAD__:~1!$3D" & ^ for /F "tokens=* delims="eol^= %%i in ("!__STRING__!") do for /F "tokens=1 delims=="eol^= %%j in (".!__TAIL__!") do for /F "tokens=* delims="eol^= %%k in (".!__TAIL__!") do ^ endlocal & set "__STRING__=%%i" & set "__HEAD__=%%j" & set "__TAIL__=%%k" & setlocal ENABLEDELAYEDEXPANSION goto LOOP
笑叹一世浮沉 2025-01-14 12:06:07

另一种简单的方法是:

set "_args=%*"
set "_args=%_args:* =%"

echo/%_args%

备注:

  • 如果第一个参数 (%1) 被“引用”或“双引号”,则不起作用
  • 如果任何参数包含 & 则不起作用。 char
  • 参数之间的任何额外空格都不会被删除

Another easy way of doing this is:

set "_args=%*"
set "_args=%_args:* =%"

echo/%_args%

Remarks:

  • Does not work if first argument (%1) is 'quoted' or "double quoted"
  • Does not work if any argument contains the & char
  • Any extra spaces in between arguments will NOT be removed
荒路情人 2025-01-14 12:06:07

DOS/Windows 批处理编程的另一个令人讨厌的缺点...

不确定这是否实际上比这里的其他一些答案更好,但我想我会分享一些似乎对我有用的东西。该解决方案使用 FOR 循环而不是 goto,并且包含在可重用的批处理脚本中。

单独的批处理脚本,“shiftn.bat”:

@echo off
setlocal EnableDelayedExpansion
set SHIFTN=%1
FOR %%i IN (%*) DO IF !SHIFTN! GEQ 0 ( set /a SHIFTN=!SHIFTN! - 1 ) ELSE ( set SHIFTEDARGS=!SHIFTEDARGS! %%i ) 
IF "%SHIFTEDARGS%" NEQ "" echo %SHIFTEDARGS:~1%

如何在另一个批处理脚本中使用shiftn.bat;在此示例中,获取第一个(跳过的)参数之后的所有参数:

FOR /f "usebackq delims=" %%i IN (`call shiftn.bat 1 %*`) DO set SHIFTEDARGS=%%i 

也许其他人可以利用此解决方案的某些方面(或提供改进建议)。

Yet another obnoxious shortcoming of DOS/Windows batch programming...

Not sure if this is actually better than some of the other answers here but thought I'd share something that seems to be working for me. This solution uses FOR loops rather than goto, and is contained in a reusable batch script.

Separate batch script, "shiftn.bat":

@echo off
setlocal EnableDelayedExpansion
set SHIFTN=%1
FOR %%i IN (%*) DO IF !SHIFTN! GEQ 0 ( set /a SHIFTN=!SHIFTN! - 1 ) ELSE ( set SHIFTEDARGS=!SHIFTEDARGS! %%i ) 
IF "%SHIFTEDARGS%" NEQ "" echo %SHIFTEDARGS:~1%

How to use shiftn.bat in another batch script; in this example getting all arguments following the first (skipped) arg:

FOR /f "usebackq delims=" %%i IN (`call shiftn.bat 1 %*`) DO set SHIFTEDARGS=%%i 

Perhaps someone else can make use of some aspects of this solution (or offer suggestions for improvement).

鼻尖触碰 2025-01-14 12:06:07

恢复所有并修复所有问题:

set Args=%1
:Parse
shift
set First=%1
if not defined First goto :EndParse
  set Args=%Args% %First%
  goto :Parse
:EndParse

参数之间不支持空格:1 2 3 4 将是 1 2 3 4

Resume of all and fix all problems:

set Args=%1
:Parse
shift
set First=%1
if not defined First goto :EndParse
  set Args=%Args% %First%
  goto :Parse
:EndParse

Unsupport spaces between arguments: 1 2 3 4 will be 1 2 3 4

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文