Board logo

标题: [系统相关] 【已解决】不及时关闭延迟变量可能会导致 cd /d 操作失效 [打印本页]

作者: aloha20200628    时间: 2025-1-8 18:42     标题: 【已解决】不及时关闭延迟变量可能会导致 cd /d 操作失效


   近期回帖中遭遇一则怪相,在win8.1,win10系统可复现,用以下5段示例代码说明,唯独其中第5段代码中最后的 cd /d "c:\temp\" 失效,即脚本退出后当前目录仍是 "c:\temp\xxx"
   以下测试代码存为 test.bat 可在命令行直接用 test.bat n 来测试各段代码运行之后当前目录的结果,其命令行参数 n=1-5

  1. @echo off &if "%~1"=="" (goto :5) else goto :%~1
  2. :1
  3. setlocal enabledelayedexpansion
  4. cd "c:\temp\xxx"
  5. echo,!cd!
  6. cd "c:\temp\"
  7. echo,改变当前目录有效》!cd!
  8. endlocal
  9. exit/b
  10. :2
  11. setlocal enabledelayedexpansion
  12. cd "c:\temp\xxx"
  13. echo,!cd!
  14. cd "c:\temp\"
  15. echo,改变当前目录有效》!cd!
  16. endlocal
  17. exit/b
  18. :3
  19. setlocal enabledelayedexpansion
  20. cd "c:\temp\xxx"
  21. echo,!cd!
  22. endlocal
  23. cd "c:\temp\"
  24. echo,改变当前目录有效》%cd%
  25. exit/b
  26. :4
  27. cd "c:\temp\xxx"
  28. setlocal enabledelayedexpansion
  29. echo,!cd!
  30. endlocal
  31. cd "c:\temp\"
  32. echo,改变当前目录有效》%cd%
  33. exit/b
  34. :5
  35. cd /d "c:\temp\xxx"
  36. setlocal enabledelayedexpansion
  37. echo,!cd!
  38. cd /d "c:\temp\"
  39. echo,!cd!》当前显示有效》脚本退出后无效
  40. endlocal
  41. exit/b
复制代码

作者: Five66    时间: 2025-1-9 15:07

看不懂 ,看起来像cd跟cd /d的区别 , cd不受endlocal影响 而 cd /d受endlocal影响???
反正不知道endlocal对内建变量的处理方式 , 基本上不会分开来用或者依赖endlocal之后的值 , 随便了
作者: aloha20200628    时间: 2025-1-9 15:49

本帖最后由 aloha20200628 于 2025-1-9 15:50 编辑

回复 2# Five66

以前很多有关 cd 或 chdir 操作失效的原因概因未用 cd /d 参数,但本帖所谓的 cd /d 失效是与 setlocal enabeldelayedexpansion ... endlocal 有关,即一楼第五段代码运行后所示的 ‘怪状’ 》代码退出后当前目录并未被刚才调用过的 cd /d ... 操作有效改变...

作者: 77七    时间: 2025-1-9 16:29

  1. SETLOCAL /?
  2. 开始批处理文件中环境改动的本地化操作。在执行 SETLOCAL 之后
  3. 所做的环境改动只限于批处理文件。要还原原先的设置,必须执
  4. 行 ENDLOCAL。达到批处理文件结尾时,对于该批处理文件的每个
  5. 尚未执行的 SETLOCAL 命令,都会有一个隐含的 ENDLOCAL 被执行。
复制代码

区域环境
作者: aloha20200628    时间: 2025-1-9 16:51

回复 4# 77七

开关延迟变量的进退或生死变化应该是针对 !var! 变量吧,cd /d "c:\temp" 可是一个采用字面量(既非!var!亦非%var%)重置当前目录的实实在在的操作啊 ...

作者: 77七    时间: 2025-1-9 17:36

回复 5# aloha20200628


   setlocal enabledelayedexpansion 给它起个全名 区域环境和延迟变量扩展,貌似我们平时都只是 简单的说 延迟变量扩展,只强调了它的一个作用。
作者: flashercs    时间: 2025-1-9 19:41

本帖最后由 flashercs 于 2025-1-9 19:51 编辑
  1. :1
  2. echo beforelocal cd=%cd%
  3. setlocal enabledelayedexpansion
  4. cd "c:\asp\abc"
  5. echo,setlocal cd=!cd!
  6. cd "c:\asp\"
  7. echo,改变当前目录有效》!cd!
  8. endlocal
  9. echo,endlocal cd=%cd%
  10. exit /b
  11. :5
  12. cd /d "c:\asp\abc"
  13. echo beforelocal cd=%cd%
  14. setlocal enabledelayedexpansion
  15. echo,setlocal cd=!cd!
  16. cd /d "c:\asp\"
  17. echo,setlocal cd=!cd!》当前显示有效》脚本退出后无效
  18. endlocal
  19. echo,endlocal cd=%cd%
  20. exit /b
复制代码
endlocal cd始终等于beforelocal cd,没问题.
另一个脚本调用这个cdtest.bat
  1. @echo off
  2. cmd /c cdtest.bat 5
  3. echo,cmd /c cd=%cd%
  4. @REM call cdtest.bat 5
  5. @REM echo,call cd=%cd%
  6. pause
复制代码
cmd /c 与 call 脚本是有区别的.cmd /c 创建新的cmd进程,call 不会创建新cmd进程
作者: aloha20200628    时间: 2025-1-9 20:53

本帖最后由 aloha20200628 于 2025-1-9 21:20 编辑


谢谢诸位跟帖讨论 回到问题的焦点,还是用以下代码说明吧...
  1. @echo off
  2. cd /d "c:\temp\xxx"
  3. setlocal enabledelayedexpansion
  4. echo,!cd!
  5. cd /d "c:\temp\"
  6.    echo,!cd!》改变当前目录有效
  7. ::以下两式会如实 ‘列表然后删除 "c:\temp\*.txt" 文件’
  8.    dir /b/a-d *.txt
  9.    del /q *.txt
  10. endlocal
  11.    :: 以下的操作不能想当然地认为当前目录还是 "c:\temp"
  12.    echo,%cd%》自动复原为开启延迟变量前的当前目录
  13. exit/b
复制代码
可见》开启延迟变量后,无论 cd /d ... 或 dir /b/a-d ... 或 del ... 等操作结果都是如期而至,但关闭延迟变量后,当前目录会自动复原为开启延迟变量前的位置(但被删除的文件却是真的一去不复返了),有点出乎所料吧(先知先觉者除外 )以此为鉴,如从第10行之后续写代码时须谨记 ‘当前目录已被自动复原到开启延迟变量前的位置’...

作者: buyiyang    时间: 2025-1-9 21:42

可从源代码中窥见,本地化时当前目录、环境变量、命令扩展状态都会保存到当前批处理的数据结构体中,结束本地化还原。
  1. // setlocal保存环境(省略简化、注释)
  2. struct envdata *CopyEnv() ;
  3. struct batsaveddata *p;
  4. p->dircpy = mkstr(mystrlen(CurDrvDir)*sizeof(TCHAR)+sizeof(TCHAR)) ;
  5. mystrcpy(p->dircpy, CurDrvDir) ;  // 保存当前目录
  6. p->envcpy = CopyEnv() ;  // 保存当前环境变量
  7. p->fEnableExtensions = fEnableExtensions;  // 保存当前命令扩展的启用状态
  8. CurBat->saveddata[CurBat->numsavedenv] = p;  // 保存为当前索引批处理数据结构
  9. CurBat->numsavedenv += 1;  // 索引递增
  10. // endlocal恢复环境(省略简化、注释)
  11. bdat->numsavedenv -= 1;  // 索引递减
  12. p = bdat->saveddata[bdat->numsavedenv];
  13. if (p) {
  14.     // 如果当前盘与保存的盘符不同,则更改磁盘
  15.     if (CurDrvDir[0] != (TCHAR)(c = _totupper(*p->dircpy)))
  16.         ChangeDrive(c - (TCHAR) 0x40) ;
  17.     ChangeDir(p->dircpy) ;  // 恢复保存的目录
  18.     ResetEnv(p->envcpy) ;  // 恢复保存的环境变量
  19.     fEnableExtensions = p->fEnableExtensions;  // 恢复保存的命令扩展状态
  20.     bdat->saveddata[bdat->numsavedenv] = NULL ;
  21. } ;
复制代码

作者: aloha20200628    时间: 2025-1-9 23:04

本帖最后由 aloha20200628 于 2025-1-9 23:05 编辑

回复 9# buyiyang







欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2