eshell 类似 brace-expansion 复制文件脚本

原本想实现类似于 Bash 下类似 Brace Expansion 拷贝文件的脚本。


mkdir a-folder-to-be-constructed_{a,b,c}

类似于 M$ Windows 下复制文件,在连续拷贝的情况下,自动添加后缀到复制后的文件。

然后 tweak 了这个 eshell 脚本:

(defun eshell/mcp (file &rest args)
  "Brace-expansion like in eshell. Type mcp
file/directory-to-be-copy a b c ...

You will get:

file/directory-to-be-copy_a
file/directory-to-be-copy_b
file/directory-to-be-copy_c

Don't use digtial number as appendix!

Works for file and directory.
..."
  (if (file-directory-p file)
      (while args
    (let ((test (--map (s-prepend (concat (file-name-sans-extension file) "_") it) args)))
      (copy-directory file (car test))
      (setq args (cdr args)))))
  (while args
    (let ((test (--map (s-prepend (concat (file-name-sans-extension file) "_") it) args)))
      (copy-file file (concat (car test) "." (file-name-extension file)))
      (setq args (cdr args)))))

还可以写个函数、宏实现 Brace Expansion,如:

(paren-expand a (d c b) e)
;; => (ade ace abe)

然后 Eshell 中使用

$ touch (paren-expand a (d c b) e .txt)

有个难点在于不止一个大括号的情况:

~ $ echo foo_{1,2}{3,4,5}
foo_13 foo_14 foo_15 foo_23 foo_24 foo_25

Dash 的 -table 有这个功能,但结果顺序不对。


相关问题:

谢谢 @xuchunyang

能展开讲一下 paren-expand 的用法吗?

(or
 (with-temp-buffer (url-insert-file-contents "https://raw.githubusercontent.com/emacs-mirror/emacs/master/doc/misc/eshell.texi")
                   (re-search-forward "paren-expand" nil t))
 (with-temp-buffer (url-insert-file-contents "https://raw.githubusercontent.com/emacs-mirror/emacs/master/doc/emacs/emacs.texi")
                   (re-search-forward "paren-expand" nil t))
 (with-temp-buffer (url-insert-file-contents "https://raw.githubusercontent.com/emacs-mirror/emacs/master/doc/lispref/elisp.texi")
                   (re-search-forward "paren-expand" nil t)))
;; → nil

it is a vaporware, in other words it waits you write it.

I see. I’ve successfully implemented and tested it in this rep.

And it works.

Enjoy : -)

a naive implementation

(defun parse-paren-exp (list)
  (let ((syms nil))
    (dolist (i (-filter #'listp list))
      (let ((sym (gensym)))
        (setq list (-replace-first i sym list))
        (push (list sym i) syms)))
    (list list syms)))


(defun form-paren-exp (list vars)
  (let ((s (gensym)))
    (setq list `(push (concat ,@list) ,s))
    (dolist (x vars)
      (setq list `(dolist (,(car x) ,(list 'quote (cadr x)))
                    ,list)))
    `(let (,s)
       ,list
       (nreverse ,s))))

(defmacro paren-expand (&rest forms)
  (apply #'form-paren-exp (parse-paren-exp forms)))
(paren-expand "a" ("1" "2" "3") ("c" "d") ".txt")
;; => ("a1c.txt" "a1d.txt" "a2c.txt" "a2d.txt" "a3c.txt" "a3d.txt")
(pp (macroexpand '(paren-expand "a" ("1" "2" "3") ("c" "d") ".txt")))

(let
    (g26)
  (dolist
      (g24
       '("1" "2" "3"))
    (dolist
        (g25
         '("c" "d"))
      (push
       (concat "a" g24 g25 ".txt")
       g26)))
  (nreverse g26))

it’s just a trivial nested dolist combo push nreverse


bonus: APL ver.

      ⍪ {(×/⍴⍵)⍴⍵} '123' ∘.{'foo',⍺,'aa',⍵,'.jpg'} '4567'
 foo1aa4.jpg 
 foo1aa5.jpg 
 foo1aa6.jpg 
 foo1aa7.jpg 
 foo2aa4.jpg 
 foo2aa5.jpg 
 foo2aa6.jpg 
 foo2aa7.jpg 
 foo3aa4.jpg 
 foo3aa5.jpg 
 foo3aa6.jpg 
 foo3aa7.jpg 

这里的 ∘. 就是 -table

好像并沒有不对

$ echo foo_{1,2}{3,4,5}{5,6}
foo_135 foo_235 foo_145 foo_245 foo_155 foo_255 foo_136 foo_236 foo_146 foo_246 foo_156 foo_256
(loop for i in (-table-flat 'list '(1 2) '(3 4 5) '(5 6))
      collect (apply #'format "foo_%i%i%i" i))
("foo_135" "foo_235" "foo_145" "foo_245" "foo_155" "foo_255" "foo_136" "foo_236" "foo_146" "foo_246" "foo_156" "foo_256")

不過 nested brace 就沒辦法用 table 了

echo foo_{1,2}{3{a,b},4,5}
foo_13a foo_23a foo_13b foo_23b foo_14 foo_24 foo_15 foo_25

这个应该算是对于树这种数据结构的入门测试了吧

但还得分析字符串啊 还得能控制遍历的顺序呀

加一个! 可以设置优先级  优先级高的会被当做一个整体 在下次展开的时候优先级-1

{1,2}{3,4}{5,6} 会展开成
135 136 145 146 235 236 245 246

!{1,2}{3,4}{5,6} 会展开成
135 235 136 236 145 245 146 246

!{1,2}!!{3,4}{5,6} 会展开成
135 145 235 245 136 146 236 246

弄这么复杂,直接用directory-files API完事了,还支持正则匹配

你们这些写CL的用gensym的时候能不能用个有意义的prefix而不要用G? :slight_smile:

看sbcl里面的宏展开密密麻麻全是G就头大

我们读代码都是读 pattern 的,只有龙鸣才看 identifier。

Bash 给出的结果和你的不一样(如第二项):

~ $ bash
bash-3.2$ echo foo_{1,2}{3,4,5}{5,6}
foo_135 foo_136 foo_145 foo_146 foo_155 foo_156 foo_235 foo_236 foo_245 foo_246 foo_255 foo_256
bash-3.2$

搞这些春秋笔法没用 :unamused: 如果真按你说的那样就不会有人写macrostep这些辅助理解宏的东西了。

如果说macrostep这些是巫师发明给麻瓜用的辅助器具,我不可以说宏是麻瓜发明给巫师用来看猴(巫师)戏的玩具?

你说得不对

– 沃兹基 硕得

那我就不懂了,这段代码你觉得能起什么有意义的变量名,換成 a b c 就有意义了么。這论点和中文编程咋这么像呢?人家给代码起 idenifiter 是因为面向 end user。end user 都是大猪蹄子。你是个 pro 要有 pro 的器量,正经的程序员会说「这代码沒用中文我看不懂」么?你要是 end user 那还是「end user 都是大猪蹄子。」,本來就不是给你看的,为什么要看。normie 看不懂 Pricipia Mathemetica 要怪作者么?再換句說話,面向 end user 是因为写代码的時後客户就在边上看,我在这沒人付我錢为啥要白花这种心思。

(let
    (g7)
  (dolist
      (g4
       '("1" "2" "3"))
    (dolist
        (g5
         '("c" "d"))
      (dolist
          (g6
           '("g a" "h"))
        (push
         (concat "a" g4 g5 g6 ".txt")
         g7))))
  (nreverse g7))

wizard 用不用我不知道,反正我从沒用过 macrostep。不管函数也好宏也好,都是为了抽象,能从文档/trivial example 理解用法为什么还要浪費时间在研究实现细节上,Emacs 几万行 lisp 每行你都要看了才配用 Emacs?而且还要看宏展开过的版本?

只有龙鸣才看 identifier。

然而我用 fish shell 试的。

我又来指 typo 啦: 是 Principia Mathematica 哦😊

那你觉得 normie 呢?

我以为是什么俚语诶,咱英语不好,咱也不敢问啊

我觉得他的意思是 看不懂也就拼不对