shell-command-to-string 和裸的命令行解析参数的区别?

如下的命令 echo '\1'

在命令行 (M-x terminal 或者 eshell )里的结果为

\1

但是在

(shell-command-to-string "echo '\1'")

的结果为

"^A\n"

是哪里的原因呢?


有一个具体的例子:(OSX环境)

ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'

在命令行里执行结果,可以正确返回本机Ip,

但是

(shell-command-to-string 
  "ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'")

返回

"^B\n^B\n"
(insert "echo '\\1'")
echo '\1'

(insert "echo '\1'")
echo '^A'

Emacs Lisp 的 string 是有转义的。

2 个赞

如果把 \1 看成两个字符长度的字符串,在 Emacs Lisp 中,你得用 "\\1"

从 Emacs Lisp 执行 Shell 时,比较烦人的是,你有可能得要 Quote 参数两次(甚至更多)。正则表达式也有类似的问题。

2 个赞

明白了,有没有办法自动帮我Quote呢,

输入 ‘\1’ 输出 ‘\1’

毕竟Bash/Perl 的语法都没有2个\,不想维护2套脚本,如果有办法把bash的脚本转成Elisp可以执行的就好了

read-string read-buffer 之類。

如果是 ParEdit 用户的话,选中之后按 " 就完了。比如选中 \n 之后,按 " 得到 "\\n"

1 个赞

这个说法有问题:

(string= "\1" "^A") ;; -> t (^A 需要 C-q C-a 输入)

也就是说实质上 \1 已经被存成 ^A 了。输入和输出应该是下面这个。

;; 输入 "^A"
;; 输出 "\\1"

这样一来不管是 paredit 还是 read-string 都不符合需求的。

(setq str "\1") ;; 已经是 ^A 了
(let ((unread-command-events (listify-key-sequence (kbd "RET"))))
  (read-string "" nil nil str))

;; -> "^A"

;; 需求应该是 "\\1"

Eshell的 alias 应该可以满足需求,因为这个是读一个文件。然而这样可定制性就差了,比如需要动态拼接一个命令的时候,就实现不了了

从来没有直接在脚本里面输入 ^A 的道理,因为多数编辑器都直接忽略不会显示这类字符,反而会引起歧义。确保能 \1 变成 \\1 就足够了。

然后我的意思是 read-string 不是这么用的,是让你直接执行 (read-string "") 然后在 minibuffer 里面输入的。如果是整个脚本文件,就用 read-buffer 处理。

我明白你的意思,然而我想找的是一种自动化的方法,不要手动输入。

这个和 Eshell 的alias 应该是一个道理,是可以做的,然而就很难更深入的自定义了,举个例子,

ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'

这样一句命令,后面有2个bash格式的\,Eshell 可以正确执行,因为读这个Alias文件的时候,就相当于read-buffer一次了。

但是这个命令想修改就很难了,(虽然这个命令确实不怎么需要修改),比如有时候需要Emacs上下文里的变量拼接出一个命令,只能通过字符串替换的方法了。

(defun selected-to-escaped-string (beg end)
  (interactive "r")
  (let ((standard-output (current-buffer)))
    (print (buffer-substring-no-properties beg end))))
ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'
"ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\\.){3}[0-9]*).*/\\2/p'"

按需自改。

2 个赞