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