8
11
2017
4

谁又用掉了我的磁盘空间?——魔改 ncdu 来对比文件树大小变化

磁盘空间不够用了,或者只是洁癖发作想清理了,可以用 ncdu 来查看到底是什么文件占用了磁盘。ncdu 基于 ncurses,对比 du,更方便交互使用,对比 baobab 这类的 GUI 的工具,ncdu 可以在服务器、Android、树莓派、路由器等没有或者不方便有图形界面的地方跑。

但是呢,我现在有很多很多不同时间的备份,我想知道,是什么东西突然用掉了我好几百兆的空间?我是不是需要把它排除在备份之外?

所以呢,我需要一个支持对比的工具。

本来我是打算什么时候有空了自己写一个的,然而我注意到 ncdu 可以把大小信息保存在文件里。其实我只要对比两个 ncdu 产生的文件,然后照着输出一个差异文件就可以了嘛。不用自己遍历文件树,不用自己做界面,多棒!而且也不一定要像我这样有不同时间的备份才有用。可以定时跑一跑 ncdu,把导出的文件保存起来,将来随时取用。

于是有了 ncdu-diff 脚本

然而事情总是不那么顺利。输出文件拿给 ncdu 加载的时候,ncdu 报错了——它不支持负数。我给它加了支持,然后再加载,BOOM!ncdu 挂掉了……有符号整型和无符号整型的事情,还有格式化输出的事情……总之花了一天,它终于不崩溃了。补丁也放在同一仓库了。

ncdu

从上图可以看出,Android 的 app 越更新越大……以及深入之后可以发现,微信的动画表情占了我好多好多的空间,我去删掉它们……

给 Arch Linux x86_64 现成的包:下载, 签名

Category: Linux | Tags: linux python ncurses
7
31
2011
5

在 ncurses 中使用 readline

有一天,我发现了一个很好用的 Python shell——bpython。它使用了 ncurses 来做界面,使用了 pygments 来高亮代码,怎么看都比 ipython 漂亮,更不用说 Python 自己的了。不过既然它使用了 ncurses,麻烦也就来了——ncurses 不支持 readline!虽然有些模拟,但终究是不好用,M-f M-b不起使用,M-数字也不能用。于是我再次去 google 同时使用 ncurses 和 readline 这两个库的办法。

功夫不负有心人,这次终于 google 到了点有用的东西

The basic idea is to use call rl_callback_read_char() when input from the user is available (determined with select, or similar), then print 'rl_line_buffer' as you would any other string in ncurses, and optionally set A_REVERSE on the position indicated by rl_point. (or just reposition the cursor I guess, either works...)

不过可惜的是,这封邮件给出的代码在我这里并没有跑起来。其实跑起来了也用处不大,因为我需要的那部分代码独立性太差了,还是得重写。

花了一个下午,我终于弄出了一个雏形。

首先,这个rl_callback_read_char()是这么用的(文档):

#include<stdio.h>
#include<readline/readline.h>

int main(void){
  int cont = 1;

  void callback(char *text){
    if(text == NULL){
      rl_callback_handler_remove();
      putchar('\n');
      cont = 0;
    }else{
      printf("%s.\n", text);
    }
  }

  rl_callback_handler_install(">> ", callback);
  while(cont){
    rl_callback_read_char();
  }
  return 0;
}
      

首先安装个回调函数,它将在 readline 读取到一行内容时调用。当标准输入可用的时候,调用rl_callback_read_char()来读取字符。另外注意,这里我用了 gcc 的嵌套函数支持,免得弄出不少全局变量。

知道怎么用rl_callback_read_char()之后,就可以按那封邮件所说的,把它和 ncurses 联合起来了。代码修改自NCURSES Programming HOWTO。思路很简单,readline 负责读取并处理用户输入,显示是自己处理的。不过作为中文用户,纠结了半天的中文问题。最开始是有了 ncurses 之后,中文显示异常。这个是通过setlocale解决的。然后又是光标放的位置不对。于是又用上了我同样不熟悉的 wchar,自己计算光标应该放在哪里。

#define _XOPEN_SOURCE 700       /* for wcswidth and 700 is for mbsnrtowcs */
#include<wchar.h>
#include<ncurses.h>		/* ncurses.h includes stdio.h */
#include<stdlib.h>
#include<string.h>
#include<readline/readline.h>
#include<locale.h>
 
int mygetstr(char *str, int y, int x){
  WINDOW *win;
  int size, col;
  int ok = 0;
  int width;
  wchar_t wstr[80];
  char *p;
 
  getmaxyx(stdscr, size, col);
 
  void getaline(char *s){
    str = s;
    rl_callback_handler_remove();
    ok = 1;
  }
 
  rl_callback_handler_install("", getaline);
  win = newwin(1, col-x, y, x);
  while(1){
    rl_callback_read_char();
    if(ok)
      break;
    werase(win);
    strncpy(str, rl_line_buffer, 80);
    p = str;
    /* how many column chars before cursor occupies? */
    size = mbsnrtowcs(wstr, (const char**)&p, rl_point, 80, NULL);
    width = wcswidth(wstr, size);
    mvwprintw(win, 0, 0, "%s", str);
    /* put the cursor at right column */
    wmove(win, 0, width);
    wrefresh(win);
  }
  delwin(win);
  return 0;
}
 
int main(){
  char mesg[] = "Enter a string: ";
  char str[80];
  int row, col;
 
  setlocale(LC_ALL, "");        /* make ncurses handle Chinese correctly */
  initscr();
  getmaxyx(stdscr, row, col);
  mvprintw(row / 2, (col - strlen(mesg)) / 2, "%s", mesg);
  refresh();
  mygetstr(str, row / 2, (col + strlen(mesg)) / 2);
  mvprintw(LINES - 2, 0, "You Entered: %s", str);
  getch();
  endwin();
 
  return 0;
}
      

注意:此代码只是演示用,缓冲区溢出什么的我都没处理。

终于搞定了 C 下结合两者的使用,接下来 Python 版思路是有了,但因为其标准库 readline 中没有提供rl_callback_read_char()函数,所以只能用 ctypes 了。下面是在 Python 里使用rl_callback_read_char()的示例,ncurses 部分我暂时不想折腾了。

#!/usr/bin/env python3
# vim:fileencoding=utf-8

import sys
import readline
import ctypes
import ctypes.util

rllib_path = ctypes.util.find_library('readline')
rllib = ctypes.CDLL(rllib_path)

def callback(s):
  if s is None:
    rllib.rl_callback_handler_remove()
    sys.stdout.write('\n')
    sys.exit()
  elif not s:
    pass
  else:
    print('%s.' % s.decode())
    # 这样也可以
    # print(readline.get_line_buffer())

cbfunc = ctypes.CFUNCTYPE(None, ctypes.c_char_p)

rllib.rl_callback_handler_install.restype = None
rllib.rl_callback_handler_install(ctypes.c_char_p(b">> "), cbfunc(callback))

while True:
  rllib.rl_callback_read_char()
      

2011年8月4日更新:今天终于完成了个 quick and dirty 的 Python 版:

#!/usr/bin/env python3
# vim:fileencoding=utf-8

import sys
import readline
import ctypes
import ctypes.util
import curses
import struct
from 字符集 import width
 
rllib_path = ctypes.util.find_library('readline')
rllib = ctypes.CDLL(rllib_path)
 
def getstr(win, y, x):
  _, col = win.getmaxyx()
  inputbox = curses.newwin(1, col-x, y, x)
  ret = ''
  ok = False
  def callback(s):
    nonlocal ok, ret
    if s is None:
      rllib.rl_callback_handler_remove()
      raise EOFError
    elif not s:
      ok = True
    else:
      ret = s.decode()
      ok = True
  
  cbfunc = ctypes.CFUNCTYPE(None, ctypes.c_char_p)
  
  rllib.rl_callback_handler_install.restype = None
  rllib.rl_callback_handler_install(ctypes.c_char_p(b""), cbfunc(callback))
  
  while True:
    rllib.rl_callback_read_char()
    if ok:
      break
    inputbox.erase()
    # 这样获取的值不对。。。
    # bbuf = ctypes.string_at(rllib.rl_line_buffer)
    buf = readline.get_line_buffer()
    bbuf = buf.encode()
    inputbox.addstr(0, 0, buf)
    rl_point = struct.unpack('I', ctypes.string_at(rllib.rl_point, 4))[0]
    w = width(bbuf[:rl_point].decode())
    inputbox.move(0, w)
    inputbox.refresh()

  del inputbox

  return ret

msg = '输入字符串:'
win = curses.initscr()
curses.noecho()
row, col = win.getmaxyx()
win.addstr(row // 2, (col - width(msg)) // 2, msg)
win.refresh()
s = getstr(win, row // 2, (col + width(msg)) // 2)
win.addstr(row - 2, 0, '你输入了: ' + s)
win.getch()
curses.endwin()
      
Category: Linux | Tags: C代码 ncurses python readline

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com