【问题标题】:Why does %date% produce a different result in batch file executed as scheduled task?为什么 %date% 在作为计划任务执行的批处理文件中会产生不同的结果?
【发布时间】:2017-06-20 23:49:01
【问题描述】:

批处理文件正在运行 robocopy 并创建一个名为 robocopyServer_%date%.txt 的日志文件。

手动运行批处理文件时,结果为robocopyServer_yyyy-mm-dd.txt

当批处理文件从 Windows Server 2016 上的计划任务运行时,结果为 robocopyServer_Tue(或星期几)并且没有文件扩展名。

此批处理文件在 Windows Server 2012 R2 上完美运行(手动或按计划任务)。

【问题讨论】:

  • 我不确定我有一个可行的答案。不幸的是,我的唯一一台 Server 2016 机器刚刚死机(主板),所以我无法测试。而且我还没有看到任何关于为什么手动运行作业与在任务管理器下运行作业不同的想法——这对我来说是零意义。
  • 当我让这台被炸毁的机器重新启动并运行时,我会检查一下。我以为我正在使用相同的用户 ID 运行计划任务,但可能不是。至少它给了我一个合理的理由来说明手动运行和计划任务运行之间的不同行为。非常感谢。

标签: windows batch-file


【解决方案1】:

%date% 或 date /t 并不总是可靠的,因为它取决于区域和用户设置。以下是获取普遍有效日期的方法:

for /f %%# in ('wmic os get localdatetime^|findstr .') do if "%%#" neq "" set date=%%#
set date=%date:~,4%-%date:~4,2%-%date:~6,2%
echo %date%

这将以 yyyy-mm-dd 格式回显当前日期。

【讨论】:

  • 你不应该给名为date的变量赋值,因为这是一个内置的动态变量,不能再访问;您应该为变量使用不同的名称...
  • 因为它是我设置区域日期设置 (yyyy-mm-dd) 的服务器,所以不必为了获取日期而费尽心思。我不明白为什么从计划任务运行它会产生与手动运行不同的结果。
【解决方案2】:

环境变量 DATETIME 的字符串格式取决于为所用帐户定义的 Windows 区域设置,即为所用帐户配置的国家/地区运行批处理文件,因为国家决定日期和时间格式。

可以使用Windows Management Instrumentation Command线工具WMIC获取区域独立的日期/时间字符串。

命令行

wmic OS GET LocalDateTime /VALUE

输出 UTF-16 Little Endian 编码例如:

 
 
LocalDateTime=20170621095402.953000+120
 
 

有两个空行,然后是当前本地日期/时间的行,格式为 yyyyMMddHHmmss.microsecond±UTC 偏移量,以分钟为单位,还有两个空行。

这些数据可以与这样的批处理代码一起使用:

@echo off
for /F "tokens=2 delims==." %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "FileNameDate=%%I"
set "FileNameDate=%FileNameDate:~0,4%-%FileNameDate:~4,2%-%FileNameDate:~6,2%"
echo %FileNameDate%

对文件和文件夹名称感兴趣的只是等号和小数点之间的日期/时间字符串,这是使用 for /F 选项 tokens=2 delims==.20170621095402 分配给循环变量 @987654331 的原因@ 其值分配在环境变量 FileNameDate 旁边。

环境变量 FileNameDate 使用字符串替换重新格式化,以仅获取格式为 yyyy-MM-dd 的日期字符串,该日期字符串是用于验证的输出,从而导致输出 2017-06-21

使用 WMIC 获取本地日期/时间的优势在于独立于 Windows 区域设置。缺点是与使用 DATETIME 环境变量(只需几次访问)相比,命令执行需要相当长的时间(1 到 2 秒)微秒。

FOR 命令在正确解析 Unicode 输出时出现问题。它将 WMIC 输出末尾的字节序列 0D 00 0A 00 0D 00 0A 00 解释为 0D 0D 0A 的两个空行,即两个回车和一个换行。这导致将 WMIC 输出末尾的最后两个空行解释为单行,并将单个回车符作为字符串。

这通常是一个问题,因为 set "EnvironmentVariable=%%I" 的结果是 %%I 在删除之前已经定义的具有正确值的环境变量时扩展为仅回车。

有多种解决方案可以解决此命令 FOR 的 Unicode 解析错误。将值分配给环境变量后,可以附加 & goto Label 以退出循环并跳转到 FOR 循环下方的 :Label,以避免遇到此问题。 p>

另一种解决方案是此代码中使用的解决方案。由于使用了 WMIC 选项/VALUE,属性的名称及其值在同一行输出。命令 FOR 运行命令 SET 因为 tokens=2 仅当它可以使用等号和点作为分隔符将当前行拆分为至少 2 个子字符串(标记)时因为delims==.。但是 WMIC 输出的错误解析结束是 FOR 仅包含回车符的行,因此没有第二个标记。因此,FOR命令也会在此处忽略错误解析的空行。

请参阅How to correct variable overwriting misbehavior when parsing output?cmd is somehow writing Chinese text as output,详细了解FOR 对UTF-16 LE 编码输出的解析问题。

要了解所使用的命令及其工作原理,请打开命令提示符窗口,在其中执行以下命令,并仔细阅读每个命令显示的所有帮助页面。

  • echo /?
  • for /?
  • set /?
  • wmic /?
  • wmic os /?
  • wmic os get /?
  • wmic os get localdatetime /?

PS:还有许多其他解决方案可以以特定格式获取当前日期/时间,独立于为已用帐户配置的国家/地区。其中一些比wmic 的使用更快。所有这些替代解决方案都可以在以下答案中找到:

How do I get current date/time on the Windows command line in a suitable format for usage in a file/folder name?

【讨论】:

    【解决方案3】:

    您必须为用于运行任务的用户创建一个 Windows 配置文件,并在该配置文件下配置区域设置和日期格式。

    【讨论】:

      【解决方案4】:

      我遇到了与 OP 类似的问题,我喜欢使用 wmic @Regejok 的方法。这是我处理 DOS DATE 返回的唯一字符串的解决方案(美国解决方案 - 抱歉,没有其他区域的操作系统可供测试,但可以很快为其他区域重构)。

      我的服务器为 %DATE% (date /t) 返回了不同的字符串。

      例如。 1 - Wed 07/04/2018(完美的细绳)

      例如。 2 - 07/04/2018(另一个完美的字符串,但由于子字符串引用而使我的批处理脚本失败)

      我正在使用此脚本从 DATE 和 TIME 中选择子字符串来创建用于记录和归档文件的日期/时间戳变量:

      原始脚本

      @echo off
      set HH=%TIME:~0,1%
      if "%HH%"==" " goto addzero
      goto hourOK
      :addzero
      set HH=0%TIME:~1,1%
      goto end
      :hourOK
      set HH=%TIME:~0,2%
      :end
      
      set mn=%TIME:~3,2%
      set SS=%TIME:~6,2%
      set ms=%TIME:~9,2%
      set MM=%DATE:~4,2%
      set DD=%DATE:~7,2%
      set YY=%DATE:~-2%
      set CCYY=%DATE:~-4%
      set dts=%CCYY%%MM%%DD%%HH%%mn%%SS%%ms%
      set dateonly=%CCYY%%MM%%DD%
      set monthlog=%CCYY%%MM%
      @echo on
      

      请不要评判代码。 :) 它变得更好了。

      “2018 年 7 月 4 日星期三”的结果:

      C:\>set HH=
      C:\>if " " == " " goto addzero
      C:\>set HH=09
      C:\>goto end
      C:\>set mn=30
      C:\>set SS=49
      C:\>set ms=69
      C:\>set MM=07
      C:\>set DD=04
      C:\>set YY=18
      C:\>set CCYY=2018
      C:\>set dts=2018070409304969
      C:\>set dateonly=20180704
      C:\>set monthlog=201807
      

      这对前任很有效。 1 个字符串。我跳过了简短的一天(星期三),一切都很好。直到我遇到返回 ex 的服务器。 2.

      “2018 年 7 月 4 日”的失败结果:

      C:\>set HH=
      C:\>if " " == " " goto addzero
      C:\>set HH=09
      C:\>goto end
      C:\>set mn=31
      C:\>set SS=48
      C:\>set ms=59
      C:\>set MM=4/
      C:\>set DD=01
      C:\>set YY=18
      C:\>set CCYY=2018
      C:\>set dts=20184/0109314859
      C:\>set dateonly=20184/01
      C:\>set monthlog=20184/
      

      所有日期/时间戳都包含一个“/”(文件命名失败)。这都是由于 DATE 字符串差异和子字符串引用造成的。时间很好。 即使月份 (MM) 和日期 (DD) 字符串出现故障,我注意到 2 位数和 4 位数年份变量都很好(YY 和 CCYY)。如何?负引用。 这成为我最终脚本的最大修复。连同字符串替换。

      我的新脚本 (我仍然喜欢使用 wmic 的方法,这只是允许 DATE 仅使用 DOS 的 2 种不同返回类型的另一种方法)

      01  @echo off
      02  set str=%DATE%      
      03  set str=%str:/=%     
      04 
      05  set MM=%str:~-8,2%  
      06  set DD=%str:~-6,2%  
      07  set YY=%str:~-2%    
      08  set CCYY=%str:~-4%  
      09
      10  set HH=%TIME:~0,2%  
      11  set HH=%HH: =0%     
      12  set mn=%TIME:~3,2%  
      13  set SS=%TIME:~6,2%  
      14  set ms=%TIME:~9,2%  
      15
      16  set _dts=%CCYY%%MM%%DD%%HH%%mn%%SS%%ms%
      17  set _dateonly=%CCYY%%MM%%DD%
      18  set _monthlog=%CCYY%%MM%
      19  @echo on
      

      注意事项:

      第 2 行:

      'str' var holds the value returned from DATE. 
      Could be formatted like ex. 1 or ex. 2, does not matter
      

      第 3 行:

      this is string replacement, all '/' replaced with '' (null). 
      ex. 1 - 'Wed 07/04/2018' >> 'Wed 07042018', ex. 2 - '07/04/2018' >> '07042018'
      

      第 5 行:

      this is where negative referencing made both date strings equal (in a sense), 
      month (MM) starts at '-8' characters from the end of the string in both 
      examples, length of '2'
      

      第 6 行:

      Same with day (DD), '-6' characters from end of string, length of '2'
      

      第 7 行:

      2-digit year (YY) works as it did before (already used negative referencing)
      

      第 8 行:

      4-digit year (CCYY) works as it did before as well
      

      第 10 行:

      TIME always returns 2-digits in its hour (HH) position, it just doesn't have
      a leading '0' if less than 10, what it does return is a leading space 
      i.e. %TIME:~0,2% will return ' 9' for 9:00 AM (not desired for date/timestamps)
      

      第 11 行:

      string replacement to the rescue again, replace all spaces with '0', 
      turns the ' 9' to '09' (exactly what we want)
      

      第 12 行:

      minutes, seconds, milliseconds all work as they did previously, using 
      sub-strings to avoid the colons (':') & the period ('.') in the time stamp 
      i.e. 10:01:26.29
      

      新脚本的结果:

      C:\>set str=07/04/2018
      C:\>set str=07042018
      C:\>set MM=07
      C:\>set DD=04
      C:\>set YY=18
      C:\>set CCYY=2018
      C:\>set HH= 9
      C:\>set HH=09
      C:\>set mn=42
      C:\>set SS=26
      C:\>set ms=48
      C:\>set _dts=2018070409422648
      C:\>set _dateonly=20180704
      C:\>set _monthlog=201807
      
      
      C:\>set str=Wed 07/04/2018
      C:\>set str=Wed 07042018
      C:\>set MM=07
      C:\>set DD=04
      C:\>set YY=18
      C:\>set CCYY=2018
      C:\>set HH=10
      C:\>set HH=10
      C:\>set mn=50
      C:\>set SS=55
      C:\>set ms=48
      C:\>set dts=2018070410505548
      C:\>set dateonly=20180704
      C:\>set monthlog=201807
      

      希望这会有所帮助。它不是独一无二的,也不是惊天动地的,只是另一种方式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-02
        • 2015-06-25
        • 2017-02-06
        • 2013-06-11
        • 2015-11-21
        • 2014-09-14
        • 2013-03-01
        相关资源
        最近更新 更多