如何实现类似xxd的函数?

我目前在用emacs lisp写bindat相关的东西,需要有一个类似xxd的函数来看看数据的详细情况,但是找了一圈网上搜到的都是hexl-mode的。 我的目标能达到这样就够了:

(my-xxd “string”)

7374 7269 6e67

总之我搞不懂要怎么一个字节一个字节的读。 请问这个函数要怎么写?

没人回。。自己勉强搞了一个:

(seq-map (lambda (b) (format “%02X” b)) (vconcat response))

方法很多。

vconcat 显示 10 进制,人人都熟悉:

(vconcat "hello")
;; => [104 101 108 108 111]

如果你的数据已经打印在 buffer 里了,用 nhexl-mode 最好了,还可以直接用 xxd、hexdump 等工具,也很容易(比如用 M-|)。

(call-process-region "hello" nil "xxd" nil t)
00000000: 6865 6c6c 6f                             hello

想从 Lisp 处理的话,就用你提到的方法,把字符串或者 Vector 当作 bytes。

(mapcar (lambda (x) (format "%02X" x)) "hello")
;; => ("68" "65" "6C" "6C" "6F")

顺便附上关于 bindat 的几个例子,来自我的 elisp-demos 项目 :

#+BEGIN_SRC elisp
(bindat-unpack '((dest-ip ip)
                 (src-ip ip)
                 (dest-port u16)
                 (src-port u16))
               [192 168 1 100 192 168 1 101 4 210 17 215])
#+END_SRC

#+RESULTS:
: ((src-port . 4567)
:  (dest-port . 1234)
:  (src-ip .
:          [192 168 1 101])
:  (dest-ip .
:           [192 168 1 100]))

#+BEGIN_SRC elisp
;; socks4: first packet to server
(bindat-unpack
 '((VER u8)
   (CMD u8)
   (DSTPORT u16)
   (DSTIP ip)
   (ID strz (eval (- (length bindat-raw) bindat-idx))))
 [#x04 #x01 #x00 #x50 #x5d #xb8 #xd8 #x22 #x46 #x72 #x65 #x64 #x00])
#+END_SRC

#+RESULTS:
: ((ID . "Fred")
:  (DSTIP .
:         [93 184 216 34])
:  (DSTPORT . 80)
:  (CMD . 1)
:  (VER . 4))

#+BEGIN_SRC elisp
(string-to-vector
 (bindat-pack '((dest-ip ip)
                (src-ip ip)
                (dest-port u16)
                (src-port u16))
              '((dest-ip   . [192 168 1 100])
                (src-ip    . [192 168 1 101])
                (dest-port . 1234)
                (src-port  . 4567))))
#+END_SRC

#+RESULTS:
: [192 168 1 100 192 168 1 101 4 210 17 215]
3 个赞

可以直接seq-map,不用转vector。另外建议先encode成binary

Emacs会按字符的方式处理多字节文本,这意味着多字节直接转vector不是bytes而是vector of unicode codepoint。

(vconcat "我可以吞下玻璃")
;; => [25105 21487 20197 21534 19979 29627 29827]
(vconcat (encode-coding-string "我可以吞下玻璃" 'binary))
;; => [230 136 145 229 143 175 228 187 165 229 144 158 ...]

所以最好应该用下面的实现

(defun xxd (str)
  (seq-map (lambda (char) (format "%02x" char))
           (encode-coding-string str 'binary)))

(xxd "我好了")
;; => ("e6" "88" "91" "e5" "a5" "bd" "e4" "ba" "86")

鉴于你提到处理bindat,那么顺带说一句,从文件读取bindata的最佳实现是用f.elf-read-bytes

https://github.com/rejeep/f.el/blob/master/README.md#f-read-bytes-path

2 个赞

哇,大佬们出现了。

其实我是要写一个rcon协议的实现,用来控制MC服务端的。这个协议难度我觉得作为入门刚好。

Source RCON Protocol - Valve Developer Community

为了查看服务端的回应我还把返回的数据再重新发到本地用xxd看,真的太麻烦了。

我之后重写一个函数再发上来。

update: 1. 换成循环 2. 去掉显示spec功能 一个临时版本,后续加上根据spec显示对应字段的进制功能。

(cl-defun my-xxd (data &optional &key (groupsize 2))
  "This function is used to check packet made by `rcon-make-payload'.
It act like xxd in linux, and support an option: groupsize."
  (if (stringp data) (my-xxd (seq-map (lambda (byte) (format "%02X" byte))
				      (encode-coding-string data 'binary))
			     :groupsize groupsize)
    (when (not (null data))
      (let ((len (length data))
	    (line 0) (gctr 0) (base 16))
	(princ (format "%08X: " line))
	(dotimes (ctr len)
	  (cl-incf gctr)
	  (let ((rmd (mod ctr base)))
	    (princ (format "%s" (nth ctr data)))
	    (when (= gctr groupsize)
	      (princ " ")
	      (setq gctr 0))
	    (when (= rmd (1- base))
	      (setq gctr 0 line (1+ line))
	      (princ (format "\n%08X: " line)))))))))

调用与输出:

(my-xxd (rcon-make-payload :body "passwrd" :type rcon-type-auth) :groupsize 4)
00000000: 11000000 4A000000 03000000 70617373 
00000001: 77726400 00
1 个赞