0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Binary file editor like vi 'bi'

Last updated at Posted at 2025-03-29

 viにインターフェースを似せて作ったバイナリファイルエディタです。僕が1991年12月にASCII-netにMS-DOS用として載せたのが始まりでした。あれから33年経っています。1996年にlinux用にviにインターフェースを似せて作られたbviがGerhard Buergmannによって発表されましたが、僕の方が早い。最初のバージョンはCによって作られましたが、このバージョンはpythonで書かれています。MS-DOS用は完成していますが、linux用に鋭意バージョンアップ中です。@のついているコマンドが現在linuxでサポートされているコマンドです。これからサポートするコマンドも増やす予定です。まだまだ不具合も多いです。アルファバージョンです。デバッグしなければいけません。

bi.doc

    ******  vi ライクバイナリファイルエディタ 'bi'  ******
                   vi like binary file editor 'bi'

                        Programmed by T.Maekawa (GAR)
                                     net36202

Overview
--------

    BI is a binary file editor designed to mimic the interface of the
    UNIX editor 'vi'. The name (BI) is an abbreviation for Binary file
    editor like vI. 

★コマンドリファレンス                        ★Command Reference

     ◎エディットモードで使えるコマンド------------------ Commands on edit mode.

@   <hex-key>       ----- set data
@   hjkl            ----- move cursor
@   ^F ^B           ----- move by a page ( 256 bytes )
@   ^D ^U           ----- move by half a page ( 128 bytes )
@   ^L              ----- repaint screen.
@   ^               ----- jump to the left end of line
@   $               ----- jump to the right end of line
@   m[a-z]          ----- mark currrent position
@   '[a-z]          ----- jump to marked point
    /               ----- search string forward
    ?               ----- search string backward
    //xx xx xx ...  ----- search binary data forward
    ??xx xx xx ...  ----- search binary data backward
    n               ----- search the next 
    N               ----- search the last
@   M               ----- display marks

@   p               ----- paste yank buffer (overwrite)
@   P               ----- paste yank buffer (insert)
@   q               ----- quit
@   x               ----- delete a byte
@   Z               ----- write and quit

@   :               ----- to command line mode

    ◎コマンドラインで使えるコマンド------------------------On command line mode

@   [offset]                ----- jump to the address
    [offset]Sstring         ----- insert string before [offset]
    [offset]sstring         ----- overwrite string on and after [offset]

    [offset]Rfilename       ----- read file (insert) before [offset]
    [offset]rfilename       ----- read file (overwrite) on and after [offset]
    [offset] f <len>,<data> ----- fill with <data> (by length)
    <start>,<end>f<xx>      ----- fill with xx (by range)
    [offset]i<len>,[data]   ----- insert data
    [offset] d <len>        ----- delete by length

@   <start>,<end> d         ----- delete by range
@   <start>,<end>y          ----- yank to yankbuffer
    <start>,<end>m<destination>   ----- move data
    <start>,<end> c <destination> ----- copy data (data will be yanked)
    <start>,<end>w<filename>      ----- write data on file
@   !<string>               ----- invoke shell
@   q                       ----- quit
@   q!                      ----- overriding quit
@   wq,wq!                  ----- write and quit
@   w <filename>            ----- write file
@   <CR> without any command or <ESC>   ----- return to on-screen mode


      サーチコマンドでは、正規表現は使えません。

      [] で囲まれた値は省略可能です。省略されたときにはカレントポジション
    がその値となります。

    コマンドラインでの値の指定は16進数値、先頭に'#'の付いた10進数、または、
    '[a-z]でマークの位置を、^でファイル先頭、.でカーソルの現在位置、
    $でファイル終端を与える事ができます。

      Search commands cannot handle regular expressions yet, Sorry.

      The values enclosed with `[]` can be left out, when
    these commands above take the current position as the value omitted. 

      On command line, you've got to give values in hexadecimal.

    And you can also give values with '[a-z] as marked position,
    ^ as the top of file, . as the current position, and $ as the bottom
    of file.


★注意                                    ★Attention

    It doesn't support undo command yet.

    @のついているコマンドが現在linuxでサポートされているコマンドです。
    鋭意バージョンアップ中

------ HISTORY -----

1991-12-4   ハードディスクをバックアップしたフロッピーのセクタが飛ぶ。
1991-12-5   DUMP とCのプログラムで泣く泣くファイル修復。
1991-12-6   ファイルエディタ作成開始。 泥縄とはこのことだ。
1991-12-7   コーディング。
1991-12-8   コーディング。
1991-12-9   一応完成。
1991-12-10  ディスクフル時のエラーチェックを追加。
1991-12-20? ASCII-pcs のjunk.testに載せるがすぐメンテで消される。
1992-01-18  qとq!とwqの区別をつける。bi に改名。
1992-01-23  ドキュメント書き変え            version 0.9999
1992-02-05  wqのバグ修正                version 0.99992
1992-02-10  メモリアロケーションエラーのバグ修正    version 0.99998
            ^D,^U コマンド追加
2025-03-29  linux version 1.98
2025-03-30  linux version 1.989 ちょっとデバッグ、xコマンド、Mコマンド、
            'コマンド、mコマンドを追加。
2025-03-31  linux version 1.9893 command lineコマンドを追加。!,w,q,wq,wq!,
            w<file>コマンドを追加。
2025-04-01  linux version 1.98951 y,d,p,Pコマンド追加

--------------------

        と、いうわけで、もし蟲を見つけた方がいらっしゃいましたら
        ぜひ御一報下さい。

★免責

    このプログラムを運用した結果についての責任は一切負いません。
      I won't owe any responsibility for the result of application of
    this program. 

コード

bi.py
#!/usr/bin/python3
import sys
import tty
import termios
import string
import copy
import os
ESC='\033['
LENONSCR=(20*16)
BOTTOMLN=23
UNKNOWN=0xffffffffffffffffffffffffffffffff
mem=[]
yank=[]
coltab=[0,1,4,5,2,6,3,7]
filename=""
modified=False
newfile=False
homeaddr=0
insmod=False
curx=0
cury=0
mark=[UNKNOWN] * 26

def escup(n=1):
    print(f"{ESC}{n}A",end='')

def escdown(n=1):
    print(f"{ESC}{n}B",end='')

def escright(n=1):
    print(f"{ESC}{n}C",end='')

def escleft(n=1):
    print(f"{ESC}{n}D",end='',flush=True)

def esclocate(x=0,y=0):
    print(f"{ESC}{y+1};{x+1}H",end='',flush=True)

def escscrollup(n=1):
    print(f"{ESC}{n}S",end='')

def escscrolldown(n=1):
    print(f"{ESC}{n}T",end='')

def escclear():
    print(f"{ESC}2J",end='')
    esclocate()

def esccolor(col1=7,col2=0):
    print(f"{ESC}3{coltab[col1]}m{ESC}4{coltab[col2]}m",end='')

def escresetcolor():
    print(f"{ESC}0m",end='')

def getch():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    tty.setraw(sys.stdin.fileno())
    ch = sys.stdin.read(1)
    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch

def putch(c):
    print(c,end='',flush=True)

def getln():
    s=""
    while True:
        ch=getch()
        if ch=='\033':
            return s
        elif ch==chr(13):
            return s
        elif ch==chr(0x7f):
            if s!='':
                escleft()
                putch(' ')
                escleft()
                s=s[:len(s)-1]
        else:
            putch(ch)
            s+=ch

def skipspc(s,idx):
    while idx<len(s) and s[idx]==' ':
        idx+=1
    return idx

def print_title():
    global filename,modified,insmod,mem
    esclocate(0,0)
    esccolor(6)
    print(f"bi version 0.98 by T.Maekawa                                               {"ins" if insmod else "ovw"} ")
    esccolor(5)
    print(f"file:[{filename:<40}] length: {len(mem)} bytes {("not " if not modified else "")+"modified"}    ")

def repaint():
    print_title()
    esclocate(0,2)
    esccolor(4)
    print("OFFSET       +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F 0123456789ABCDEF  ")
    esccolor(7)
    addr=homeaddr
    for y in range(0x14):
        esccolor(5)
        print(f"{addr+y*16:012X} ",end='')
        esccolor(7)
        for i in range(16):
            a=y*16+i+addr
            print(f"~~ " if a>=len(mem) else f"{mem[a]:02X} ",end='')
        esccolor(6)
        for i in range(16):
            a=y*16+i+addr
            print("~" if a>=len(mem) else (chr(mem[a]) if 0x20<=mem[a]<=0x7f else "."),end='')
        print("")

def insmem(start,mem2):
    global mem,modified
    if start>=len(mem):
        for i in range(start-len(mem)+1):
            mem+=[0]
    if start<len(mem):
        mem1=[]
        mem3=[]
        for j in range(start):
            mem1+=[mem[j]]
        for j in range(len(mem)-start):
            mem3+=[mem[start+j]]
        mem=mem1+mem2+mem3
    modified=True

def delmem(start,end,yf):
    global yank,mem,modified
    length=end-start+1
    if length<=0:
        return
    if start>=len(mem):
        return
    if yf:
        yank=[]
        for j in range(start,end+1):
            if j<len(mem):
                yank+=[mem[j]]

    mem1=[]
    mem2=[]
    for j in range(start):
        mem1+=[mem[j]]
    for j in range(end+1,len(mem)):
        mem2+=[mem[j]]
    mem=mem1+mem2
    modified=True

def yankmem(start,end):
    global yank,mem,modified
    length=end-start+1
    if length<=0:
        return
    if start>=len(mem):
        return
    yank=[]
    cnt=0
    for j in range(start,end+1):
        if j<len(mem):
            cnt+=1
            yank+=[mem[j]]

    stdmm(f"{cnt} bytes yanked.")

def ovwmem(start,mem0):
    global mem,modified

    if mem0==[]:
        return

    if start+len(mem0)>len(mem):
        for j in range(start+len(mem0)-len(mem)):
            mem+=[0]

    for j in range(len(mem0)):
        if j>=len(mem):
            mem+=[mem0[j]]
        else:
            mem[start+j]=mem0[j]
    modified=True

def scrup():
    global homeaddr
    if homeaddr>=16:
        homeaddr-=16

def scrdown():
    global homeaddr
    homeaddr+=16

def fpos():
    global homeaddr,curx,cury
    return(homeaddr+curx//2+cury*16)

def inccurx():
    global curx,cury
    if curx<31:
        curx+=1
    else:
        curx=0
        if cury<19:
            cury+=1
        else:
            scrdown()

def readmem(addr):
    global mem
    if addr>=len(mem):
        return 0
    return mem[addr]

def setmem(addr,data):
    global mem
    if addr>=len(mem):
        for i in range(addr-len(mem)+1):
            mem+=[0]
    mem[addr]=data
def clrmm():
    esclocate(0,BOTTOMLN)
    esccolor(6)
    print(" "*79,end='')

def stdmm(s):
    clrmm()
    esccolor(6)
    esclocate(0,BOTTOMLN)
    print(s,end='')

def jump(addr):
    global homeaddr,curx,cury
    if addr < homeaddr or addr>=homeaddr+LENONSCR:
        homeaddr=addr & ~(0xff)
    i=addr-homeaddr
    curx=(i&0xf)*2
    cury=(i//16)

def disp_marks():
    j=0
    esclocate(0,BOTTOMLN)
    esccolor(7)
    for i in 'abcdefghijklmnopqrstuvwxyz':
        m=mark[j]
        if m==UNKNOWN:
            print(f"{i} = unknown         ",end='')
        else:
            print(f"{i} = {mark[j]:012X}    ",end='')
        j+=1
        if j%3==0:
            print()
    esccolor(4)
    print("[ hit any key ]")
    getch()
    escclear()

def invoke_shell(s):
    esccolor(7)
    print()
    s=line[1:].lstrip()
    os.system(s.lstrip())
    esccolor(4)
    print("[ Hit any key to return ]",end='',flush=True)
    getch()
    escclear()

def get_value(s,idx):
    global mem
    v=0
    idx=skipspc(s,idx)
    if idx>=len(s):
        return UNKNOWN,UNKNOWN
    if s[idx]=='$':
        idx+=1
        v=len(mem)-1
    elif s[idx]=='^':
        idx+=1
        v=0
    elif s[idx]=='.':
        idx+=1
        v=fpos()
    elif s[idx]=='\'' and 'a'<=s[idx+1]<='z':
        v=mark[ord(s[idx+1])-ord('a')]
        if v==UNKNOWN:
            stdmm("Unknown mark.")
            return UNKNOWN,UNKNOWN
        else:
            idx+=2
    elif s[idx].lower() in '0123456789abcdef':
        x=0
        while idx<len(s) and s[idx].lower() in '0123456789abcdef':
            x=16*x+int(s[idx].lower(),16)
            idx+=1
        v=x
    elif s[idx]=='#':
        x=0
        idx+=1
        while idx<len(s) and s[idx] in '0123456789':
            x=10*x+int(s[idx])
            idx+=1
        v=x
    return v,idx

def commandline():
    esclocate(0,BOTTOMLN)
    esccolor(7)
    putch(':')
    line=getln()
    if line=='':
        return -1
    if line=='q':
        if modified:
            stdmm("No write since last change. To overriding quit, use 'q!'.")
            return -1
        return 0
    elif line=='q!':
        return 0
    elif line=='wq':
        return 1
    elif line=='wq!':
        return 1
    elif line[0]=='w':
        if len(line)>=2:
            s=line[1:].lstrip()
            writefile(s)
        return -1
    elif line[0]=='!':
        if len(line)>=2:
            invoke_shell(line[1:])
            return -1
    idx=skipspc(line,0)
    x,idx=get_value(line,idx)
    x2=x

    idx=skipspc(line,idx)

    if idx==len(line) and not x==UNKNOWN:
        jump(x)
        return -1

    if idx<len(line) and line[idx]==',':
        x2,idx=get_value(line,idx+1)

    idx=skipspc(line,idx)

    if idx<len(line) and line[idx]=='d':
        delmem(x,x2,True)
        return -1
    elif idx<len(line) and line[idx]=='y':
        yankmem(x,x2)
        return -1

    stdmm("Unrecognized command.")
    return -1

def fedit():
    global yank,modified,insmod,homeaddr,curx,cury
    stroke=False
    while True:
        repaint()
        esclocate( curx//2*3+13+(curx&1),cury+3)
        ch=getch()
        if ch==chr(2):
            if homeaddr>=256:
                homeaddr-=256
            else:
                homeaddr=0
            continue
        elif ch==chr(6):
            homeaddr+=256
            continue
        elif ch==chr(0x15):
            if homeaddr>=128:
                homeaddr-=128
            else:
                homeaddr=0
            continue
        elif ch==chr(4):
            homeaddr+=128
            continue
        elif ch=='^':
            curx=0
            continue
        elif ch=='$':
            curx=30
            continue
        elif ch=='j':
            if cury<19:
                cury+=1
            else:
                scrdown()
            continue
        elif ch=='k':
            if cury>0:
                cury-=1
            else:
                scrup()
            continue
        elif ch=='h':
            if curx>0:
                curx-=1
            else:
                if fpos()!=0:
                    curx=31
                    if cury>0:
                        cury-=1
                    else:
                        scrup()
            continue
        elif ch=='l':
            inccurx()
            continue
        elif ch==chr(12):
            escclear()
            repaint()
            continue
        elif ch=='Z':
            return(True)
        elif ch=='q':
            return(False)
        elif ch=='M':
            disp_marks()
            continue
        elif ch=='m':
            ch=getch().lower()
            if 'a'<=ch<='z':
                mark[ord(ch)-ord('a')]=fpos()
            continue
        elif ch=='\'':
            ch=getch().lower()
            if 'a'<=ch<='z':
                jump(mark[ord(ch)-ord('a')])
            continue
        elif ch=='p':
            y=list(yank)
            ovwmem(fpos(),y)
            jump(fpos()+len(y))
            continue
        elif ch=='P':
            y=list(yank)
            insmem(fpos(),y)
            delmem(len(mem)-1,len(mem)-1,False)
            jump(fpos()+len(yank))
            continue

        clrmm()

        if ch=='i':
            insmod=not insmod
            stroke=False
        elif ch in string.hexdigits:
            addr=fpos()
            c=int("0x"+ch,16)
            sh=4 if not curx&1 else 0
            mask=0xf if not curx&1 else 0xf0
            if insmod:
                if not stroke and addr<len(mem):
                    insmem(addr,[c<<sh])
                else:
                    setmem(addr,readmem(addr)&mask|c<<sh)
                stroke=(not stroke) if not curx&1 else False
            else:
                setmem(addr,readmem(addr)&mask|c<<sh)
                modified=True
            inccurx()
        elif ch=='x':
            delmem(fpos(),fpos(),False)
        elif ch==':':
            f=commandline()
            if f==1:
                return True
            elif f==0:
                return False

def readfile(fn):
    global mem,newfile
    try:
        f=open(fn,"rb")
    except:
        newfile=True
        stdmm("<new file>")
        mem=[]
    else:
        newfile=False
        mem=list(f.read())
        f.close()

def writefile(fn):
    global mem,newfile
    f=open(fn,"wb")
    f.write(bytes(mem))
    f.close()

def main():
    global filename
    if len(sys.argv)<=1:
        print("Usage: bi file")
        return
    filename=sys.argv[1]
    readfile(filename)
    f=fedit()
    if f:
        writefile(filename)

if __name__=="__main__":
    main()
    exit(0)

インストール&実行

#### installation
chmod +x bi.py
sudo cp bi.py /usr/bin/bi

#### execution
bi file
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?