【脚本相关】关于callSystem踩坑记录与解决方法探索

来看看 system.callSystem 在文档里的描述,这边只讨论CMD用法,下面梳理了几种情况,感兴趣可以继续往下看。欢迎指出遗落错误之处

需要注意:脚本传递中文给CMD会造成乱码,包括文件路径带有中文,这是AE(UTF8)与CMD(GBK)编码不同导致的。

 

 

我这边写了一个脚本字符串占位函数,这样就不用+"’来痛苦连接了

function str2cmd(str, list) {
    var cmd = str;
    for (i = 0, ii = list.length; i < ii; i++) {
        var tmp = '"' + list[i] + '"';
        // var tmp = list[i];
        cmd = cmd.toString().replace("${" + (i + 1) + "}", tmp);
    }
    return cmd;
}

var AutoHotkey = "AutoHotkeyU64.exe";
var autoSavePreset = "autoSavePreset.ahk";
var jsxPath = File($.fileName).parent.fsName;
var ahkPath = jsxPath + "\\(pwcode)\\AutoHotkey\\";

var cmd = str2cmd('cmd /c start "" /d ${1} ${2} ${3}', [ahkPath, AutoHotkey, autoSavePreset]);

 

占位符格式是  ${Number}  其中Number是数字,从1开始,有几个参数就有几个数字,这样比较人性化。

str2cmd() 函数只有两个参数,第一个参数是字符串,第二个是一个数组,存放占位对象的字符串数据。其实函数功能就是占位对象的字符串内容替换掉${Number}而已,非常简单。

 

首先代码语句示范如下,其中用双引号括起来的字符串是命令行调用指令,那么具体该怎么写呢?

var cmd = "";
system.callSystem(cmd);

 

我们不先急着写 cmd.exe /c 指令,而是试试用 echo 在控制台打印消息,因为echo本身是cmd的内置命令之一,算是测试这个函数是不是本身调用CMD然后运行传入的字符串,为此我们需要在语句后面添加 & pause 来暂停观察。

var cmd = "echo Hello jsx & pause";

 

那么会发生什么呢?AE弹出报错(截图如下),我特意用手机手写乱码内容,然后找了一个在线恢复乱码网站,报错信息翻译过来是“系统找不到指定的文件”。

至于为啥会乱码,可能跟CMD本身的编码与AE编码不一致有关,CMD使用GBK,而AE使用UTF-8。

【脚本相关】关于callSystem踩坑记录与解决方法探索

 

 

 

 

 

 

回归正题,我们可以肯定函数本身没有调用CMD,如果我们想要用CMD写指令,需要手动添加 cmd.exe /c 来运行指令。还有一部分参数目前用不到就不放了

cmd   [/c | /k]   [<string>]

/c  执行字符串指令,结束后关闭命令窗口,大概是close缩写。

/k  执行字符串,结束后不关闭命令窗口,大概是keep缩写,一般情况用不到这个指令。

<string>是字符串指令,可以发现我们用system.callSystem()函数传入的内容其实就是字符串,等于字符串里面还有一个字符串。

因为字符串指令需要用双引号括起来,可以简写成外面的字符串用单引号,避免冲突。如果想全部用双引号,需要在双引号前面添加 \ 转义一下,比如 \" 。另外文件路径的\也需要转义成\

var cmd = 'cmd.exe /c "echo Hello jsx & pause"'; // 推荐这个
var cmd = "cmd.exe /c \"echo Hello jsx & pause\""; // 容易搞混

 

那么如果<string>里面还有字符串呢?为了方便观察我们先把双引号改成[[" "]],如下:

system.callSystem([[“ cmd /c  [[“  [[“C:\\Program Files\\~\\xxx.exe”]]  [[“D:\\my docs\\new title.txt”> “]]  “]])

可以看的出来,CMD的双引号只要成双成对就可以无限嵌套。

需要注意这些特殊字符需要用双引号括起来:圆括号() 中括号[] 花括号{} 空格 不知道叫啥^ 等号= 分号; 感叹号! 单引号’ 加号+ 逗号, 反单引号` 波浪号~ 连接符& 

因为这些特殊字符在CMD里有特殊含义,会被当成运算符等指令运行的,因此需要用双引号引用起来变成普通字符串。下面简单说一下常见的特殊字符功能:

&  命令连接字符,可以在一行运行多个命令,例如 echo Hello & pause

|  管道命令,可以把左边的结果传递到右边

>  命令重定向,如果指向一个文件,那么控制台输出的内容将会保存到文件里

^  在特定字符前面添加^来转义成字符串,取消字符的功能,比如  < > | &  等等

&&  连接两个命令,当&&前的命令成功时,才执行&&后的命令

||  连接两个命令,当||前的命令失败时,才执行||后的命令

 

 

已经严重跑题了,回归正题。

 

让我们来运行代码,会发生什么呢?乌漆嘛黑的CMD根本就没弹出来,哪怕是一闪而过也没有,完全静默。Adobe可能不想让CMD窗口影响用户体验。

该函数会返回字符串指令在运行时打印(输出)在控制台窗口的内容,比如echo,像是打开其他exe程序,如果程序本身没返回数据,那AE获得到的要么是空字符串要么是undefined。

var cmd = 'cmd.exe /c "echo Hello jsx & pause"';
var str = system.callSystem(cmd);
alert("返回参数的数据类型:" + typeof str + "\n返回参数的数据内容:" + str);

突然发现参数用了 /c 而不是 /k,但就实机测试结果而言,依然没差别 (′д` )…彡…彡Adobe可能做了特别处理。

【脚本相关】关于callSystem踩坑记录与解决方法探索

 

 

继续上面的<string>内容,字符串指令的内容总结成以下内容:

[<Command | Program>]   [<parameter1, parameter2, …>]

Command 是CMD各种内置指令,可以在CMD输入help来查看命令列表,查看某个命令具体功能可以用 help echo 或者 echo /? ,这边用echo举例子。

Program是可执行文件或程序,它会在运行的脚本当前位置、环境变量的Path位置查找对应的文件运行,可执行文件后缀常见有:COM EXE BAT CMD VBS 等等。bat批处理和vbs脚本可以做很多事情,比如用bat调用ffmpeg处理视频合并序列帧等等。

var cmd = 'cmd.exe /c "notepad"';

var cmd = 'cmd.exe /c "run.bat"';

var cmd = 'cmd.exe /c "run.vbs"';

 

<parameter> 是用来向可执行文件传递数据的,然后根据载体不同,如何处理传递参数由载体决定,比如 notepad temp.txt 是用记事本打开这个文本文件。如果提供的是相对位置,从运行的脚本当前位置查找文件,其他情况推荐用绝对路径。

var cmd = 'cmd.exe /c "notepad temp.txt"';

 

也可以省略前面的程序部分,这样的话根据传递的文件格式,按照默认程序打开该文件,比如mp4用视频播放器打开,txt用文本编辑器打开等等。

var cmd = 'cmd.exe /c "temp.txt"';

 

这边补充一些常见的路径,其中获取脚本本身路径的方法是Moelody提供的。

var projectPath = app.project.file.fsName; // 工程完整路径

var projectFolder = app.project.file.parent.fsName; // 工程所在文件夹

var projectName = File.decode(app.project.file.name); // 工程名

var jsxPath = File($.fileName).parent.fsName; // 脚本本身的路径

 

可以简写成直接可执行程序,也就是把前面的 cmd /c 删了,查找位置上面已经说过一遍了。有一个好处是,程序运行产生的返回值能直接传递回来,用 cmd /c 打开的程序已经把返回值隔断了,AE无法直接获取到返回值。如果程序和参数有空格,需要用双引号引用起来。

需要注意:传递的参数内容不能有中文,不然会导致乱码,这是因为AE使用utf8编码,而CMD使用GBK编码,编码不同导致通讯时错乱。目前可以通过用脚本把数据保存成bat批处理,然后用system.callSystem()调用批处理,也是Moelody提醒的。

var cmd = "notepad temp.txt";
var cmd = 'notepad "new temp.txt"';

 

另外前面没有可执行文件是会报错的,也就是只有<parameter>参数,报错大意是该东西不是可执行文件,上面能用默认程序打开文件是因为CMD的缘故。

var cmd = "temp.txt"; // pass

 

围绕一个 cmd /c 差不多已经讲完了,不知道你们有没有发现一个细节,以上指令存在一个问题——同步运行

也就是代码必须按照顺序运行,我们用system.callSystem打开的程序,需要等待它运行结束进程关闭后,才能继续运行脚本的下一行代码。

可以看到这时候的脚本会陷入未响应假死状态,这是在等程序结束,jsx脚本被阻塞了,程序结束后脚本会恢复正常。但也不一定,如果cmd /c和start嵌套太多写法混乱,有概率造成AE未响应崩溃!

我们可以通过alert查看函数的返回值,但必须等打开的程序结束进程才能看到alert弹窗,只有一个 cmd /c 的时候用echo显式输出内容,能够被AE捕获到(作为返回值传递回AE),如果是调用bat批处理,指令也会被捕获,除非批处理第一行添加 @echo off 

有一部分程序,我用函数打开后在任务栏常驻小图标(程序本身的功能),然后脚本也能够继续运行下一行代码,目前测试的程序不多,不知道规律是啥。这个程序是autohotkey,一个可以自定义自动化快捷键的小工具,非常强大。

 

 

有时候没有界面是一个硬伤,这时候需要引入一个新的CMD,指令如下:

var cmd = 'cmd /c cmd /c "echo Hello jsx & pause"';

【脚本相关】关于callSystem踩坑记录与解决方法探索

然后就会发现 system.callSystem() 返回值无了,是一个空字符串,echo打印的内容没有传递回来,笑嘻嘻……。

如果第二个CMD一直暂停就是不关闭,运行中的脚本会一直停留等待重新响应,未响应出现了!等待时间久了,抽风堵塞的脚本可能会把AE搞崩。

那么我们应该尽量不添加暂停的指令,比如pause或者input让用户输入内容等等,操作逻辑应该在脚本完成,底层执行让CMD后台运行即可。

 

我们来分析分析用CMD把内容拷贝到剪贴板的指令(来自copyToClipboard):

var str = "Hello jsx";
var cmd = 'cmd /c cmd /c "echo ' + str + ' | clip"';
var temp = system.callSystem(cmd);
alert(temp);

 

上面已经说过了,两个 cmd /c 能够看到CMD窗口,然后echo是把内容输出到CMD窗口,| 管道符的作用是把左边的输出内容传递到右边,clip是剪贴板的命令,我把帮助放在下面:

CLIP

描述:
将命令行工具的输出重定向到 Windows 剪贴板。这个文本输出可以被粘贴
到其他程序中。

参数列表:
/? 显示此帮助消息。

示例:
DIR | CLIP 将一份当前目录列表的副本放入 Windows 剪贴板。

CLIP < README.TXT 将 readme.txt 的一份文本放入 Windows 剪贴板。

当只有一个 cmd /c 的时候,窗口不可见,我不知道Adobe干了什么,也许把输出也给劫持了,导致 clip 无法获取到内容,因为才需要两个 cmd /c。坏处是无法把数据返回给AE,不过我们本来就是要把脚本的内容通过CMD拷贝到剪贴板,没有返回值问题也不大。

但因为两个 cmd /c 让CMD窗口变得可见了,然后指令不多运行快,会造成一个乌漆嘛黑的窗口一闪而过,很影响观感。这时候可以添加start /min让窗口最小化运行,这样一闪而过因为最小化也不会出现在用户面前,可以在任务栏看到CMD图标。

var cmd = 'cmd /c start /min /b cmd /c "echo ' + str + ' | clip"';

 

因为start是CMD的内置命令,不能独立运行,因此最低限度也要一个 cmd /c 。

附上 start 指令说明:

start   <title>   [/d <path>]   [/i]    [/min | /max]   [/wait]   [/b]   <Command | Program>]   [<parameter1> …]

  • title  标题是用双引号括起来的字符串,最低限度也要空字符串 “”;给CMD指定一个窗口栏的标题内容,一般来说这个不能省略,不然也许会有bug,比如把传递的双引号程序路径当作标题;如果有使用 /min 等参数分隔 “”可以省略。
  • /d path  指定启动目录,比如我指定在程序的位置作为启动目录,这样我后面可以只写程序名偷懒。
  • /i  默认的cmd.exe一般启动目录在用户目录,如果启用该项说明新CMD的启动目录将会变成用户目录,大概是这样;如果没有启动则使用当前环境。
  • /min  启动时以最小化窗口运行 /max 启动时以最大化窗口运行。
  • /wait  启动程序后等待应用结束,看需求了。
  • /b  打开新CMD时,将不会打开新的窗口,而是在当前窗口运行。
  • Command Program parameter  这些看上面的解释,一样的。

 

start的简洁用法:

var cmd = 'cmd /c "start www.google.com"'; // 用默认浏览器打开链接
var cmd = 'cmd /c "start msedge www.google.com"'; //指定EDGE打开

 

 

现在该说批处理了,我们可以直接运行bat,无需添加 cmd /c。

xxx.jsx

var abc = "World!";
var cmd = "run.bat " + abc;
var str = system.callSystem(cmd);
alert(str);

run.bat

@echo off

echo "Hello, %1"

 

AE获得的返回值是“Hello, World!”

%1 是占位符,表示传递进来的第一个参数,%0 表示批处理命令本身,这样我们可以操作的东西就更多了。更深入的交互。你们可以来研究研究。

 

原本还有一点点内容,但实在被这个函数的崩溃次数弄的心态崩了,不想继续写下去,有空再说。

 

给TA充电
共{{data.count}}人
人已充电
AE开发脚本开发

【脚本开发】AE菜单命令

2021-12-20 15:12:46

AE开发脚本开发

【脚本相关】借助AutoHotKey来实现保存预设自动化操作(有瑕疵)

2021-12-23 23:50:43

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
搜索