|
@@ -1,122 +1,128 @@
|
|
|
# tf Text File manipulations
|
|
|
# for micropython and other tiny environments
|
|
|
-
|
|
|
-#NOTE: the ESP8266 port cannot to \1,\2 type replacements in the s/search/replace/ operator
|
|
|
+# (c) Pat Beirne patb@pbeirne.com
|
|
|
import re,os,sys,gc
|
|
|
|
|
|
-def _file_scan(src,dest,start=1,end=0xFFFFFFFF,numbers=False,grep_func=None):
|
|
|
- #src is a filename, dst is an open handle
|
|
|
+# ====legend====
|
|
|
+# e=end
|
|
|
+# f=input file
|
|
|
+# g=output file
|
|
|
+# i=iteration variable
|
|
|
+# m=line range
|
|
|
+# r=replace text
|
|
|
+# s=start/search/string
|
|
|
+# sp=search regex
|
|
|
+
|
|
|
+def transfer(src,dest,first=1,last=0xFFFFFFFF,numbers=False,grep_func=None):
|
|
|
+ #src is a filename, dst is a handle
|
|
|
i=0
|
|
|
try:
|
|
|
with open(src) as f:
|
|
|
- for line in f:
|
|
|
+ for lin in f:
|
|
|
i=i+1
|
|
|
- if i<start or i>end:
|
|
|
+ if i<first or i>last:
|
|
|
continue
|
|
|
- if grep_func and not grep_func(line):
|
|
|
+ if grep_func and not grep_func(lin):
|
|
|
continue
|
|
|
if numbers:
|
|
|
dest.write(str(i)+' ')
|
|
|
- dest.write(line)
|
|
|
+ dest.write(lin)
|
|
|
except:
|
|
|
print("could not open file {}".format(src))
|
|
|
|
|
|
-def cp(src_f, dst_f):
|
|
|
+def cp(src,dest):
|
|
|
try:
|
|
|
- with open(dst_f,'w') as g:
|
|
|
- _file_scan(src_f,g)
|
|
|
+ with open(dest,'w') as g:
|
|
|
+ transfer(src,g)
|
|
|
except:
|
|
|
- print("could not write to file {}".format(dst_f))
|
|
|
+ print("could not write to file {}".format(dest))
|
|
|
|
|
|
def grep(filename, pattern, numbers=False):
|
|
|
m=re.compile(pattern)
|
|
|
if not m:
|
|
|
print("grep() called with invalid pattern")
|
|
|
- return None
|
|
|
- _file_scan(filename,sys.stdout,numbers=numbers,grep_func=(lambda x:m.search(x)))
|
|
|
- print()
|
|
|
+ return
|
|
|
+ transfer(filename,sys.stdout,numbers=numbers,grep_func=(lambda x:m.search(x)))
|
|
|
|
|
|
def cat(filename, first=1, last=1000000, numbers=False, title=True):
|
|
|
if title:
|
|
|
print("===={}=====".format(filename))
|
|
|
- _file_scan(filename,sys.stdout,first,last,numbers=numbers)
|
|
|
- print()
|
|
|
+ transfer(filename,sys.stdout,first,last,numbers=numbers)
|
|
|
|
|
|
-def sed(filename, sed_cmd, bak_ext="bak"):
|
|
|
- #print("sed() called with sed_cmd=<{}>".format(sed_cmd))
|
|
|
+def sed(filename, sed_cmd, bak_ext=".bak"):
|
|
|
# parse the sed_cmd
|
|
|
- # group 1,3 are the n-start, n-end group 4 is command
|
|
|
- g=re.search("^(\d*)([,-](\d+|\$))?\s*([sdaixX].*)",sed_cmd)
|
|
|
- if not g:
|
|
|
- print("sed() failed; 2nd argument must be a number followed by one of sdaixX; no changes applied")
|
|
|
- return 0,0
|
|
|
- cmd=g.group(4)
|
|
|
- #print("sed() cmd parsed into <{}>,<{}> and <{}>".format(g.group(1),g.group(3),g.group(4)))
|
|
|
-
|
|
|
- start,end=(1,1000000)
|
|
|
- if g.group(1):
|
|
|
- start=end=int(g.group(1))
|
|
|
- if g.group(3):
|
|
|
- end=1000000 if g.group(3)=='$' else int(g.group(3))
|
|
|
+ # group 1,3 are the n-start, n-end group 4 is command: aidsxX
|
|
|
+ a=re.search("^(\d*)([,-](\d+|\$))?\s*([sdaixX].*)",sed_cmd)
|
|
|
+ if not a:
|
|
|
+ print("sed() failed; 2nd argument must be a number-range followed by one of sdaixX; no changes applied")
|
|
|
+ return
|
|
|
+ cmd=a.group(4)
|
|
|
+
|
|
|
+ s,e=(1,1000000)
|
|
|
+ if a.group(1):
|
|
|
+ s=e=int(a.group(1))
|
|
|
+ if a.group(3):
|
|
|
+ e=1000000 if a.group(3)=='$' else int(a.group(3))
|
|
|
|
|
|
op=cmd[0]
|
|
|
if op not in "sdiaxX":
|
|
|
print("sed requires an operation, one of 's,d,i,a,x or X'")
|
|
|
- return 0,0
|
|
|
+ return
|
|
|
#print("sed command parser of <{}> returned {} {} {} {}".format(cmd,sr,de,ins,add))
|
|
|
- if op in "sxX" and len(cmd)<2:
|
|
|
- print("invalid sed argument")
|
|
|
- return (0,0)
|
|
|
- if op=='s':
|
|
|
- dl=cmd[1]
|
|
|
- gs=re.search("s"+dl+"([^"+dl+"]*)"+dl+"([^"+dl+"]*)"+dl,cmd)
|
|
|
- if not gs:
|
|
|
- print("invalid sed search-and-replace pattern")
|
|
|
- return (0,0)
|
|
|
- s,r = gs.group(1),gs.group(2)
|
|
|
- #print("search <{}> and replace <{}>".format(s,r))
|
|
|
- sp=re.compile(s)
|
|
|
- if op=='X' or op=='x':
|
|
|
+ if op in "sxX":
|
|
|
+ if len(cmd)<2:
|
|
|
+ print("invalid sed argument")
|
|
|
+ return
|
|
|
dl=cmd[1]
|
|
|
- gs=re.search("[xX]"+dl+"([^"+dl+"]*)"+dl,cmd)
|
|
|
+ if op=='s':
|
|
|
+ gs=re.search("s"+dl+"([^"+dl+"]*)"+dl+"([^"+dl+"]*)"+dl,cmd)
|
|
|
+ else:
|
|
|
+ gs=re.search("[xX]"+dl+"([^"+dl+"]*)"+dl,cmd)
|
|
|
if not gs:
|
|
|
print("invalid sed search pattern")
|
|
|
- return (0,0)
|
|
|
- sp=re.compile(gs.group(1))
|
|
|
+ return 0,0
|
|
|
+ if op=='s':
|
|
|
+ ss,r = gs.group(1),gs.group(2)
|
|
|
+ #print("search <{}> and replace <{}>".format(s,r))
|
|
|
+ else:
|
|
|
+ ss=gs.group(1)
|
|
|
+ #print("search <{}>".format(s))
|
|
|
+ sp=re.compile(ss)
|
|
|
|
|
|
- extra=g.group(4)[1:] + '\n'
|
|
|
+ extra=a.group(4)[1:] + '\n'
|
|
|
|
|
|
try:
|
|
|
- os.rename(filename,filename+'.'+bak_ext)
|
|
|
+ os.rename(filename,filename+bak_ext)
|
|
|
except:
|
|
|
print("problem with filename; backup failed; no changes made")
|
|
|
- return (0,0)
|
|
|
+ return
|
|
|
|
|
|
i=h=0
|
|
|
try:
|
|
|
- with open(filename+'.'+bak_ext) as d:
|
|
|
- with open(filename,'w') as f:
|
|
|
- for lin in d:
|
|
|
+ with open(filename+bak_ext) as f:
|
|
|
+ with open(filename,'w') as g:
|
|
|
+ for lin in f:
|
|
|
i=i+1
|
|
|
- m=(i>=start and i<=end)
|
|
|
+ m=(i>=s and i<=e)
|
|
|
if op=='s' and m:
|
|
|
+ lin=lin[:-1]
|
|
|
if sp.search(lin): h+=1
|
|
|
- lin=sp.sub(r,lin)
|
|
|
+ lin=sp.sub(r,lin)+'\n'
|
|
|
if op=='d' and m:
|
|
|
h+=1
|
|
|
continue # delete line
|
|
|
if op=='i' and m:
|
|
|
#print("insert a line before {} <{}>".format(i,extra))
|
|
|
- f.write(extra)
|
|
|
+ g.write(extra)
|
|
|
h+=1
|
|
|
if op in "aids":
|
|
|
- f.write(lin)
|
|
|
- elif (m and (op=='x' and sp.search(lin)) or (op=='X' and not sp.search(lin))):
|
|
|
- f.write(lin)
|
|
|
+ g.write(lin)
|
|
|
+ elif m and (op=='x' if sp.search(lin) else op=='X'):
|
|
|
+ g.write(lin)
|
|
|
h+=1
|
|
|
if op=='a' and m:
|
|
|
#print("append a line after {} <{}>".format(i,extra))
|
|
|
- f.write(extra)
|
|
|
+ g.write(extra)
|
|
|
h+=1
|
|
|
#f.write("--file modifed by sed()--\n")
|
|
|
except OSError:
|
|
@@ -125,31 +131,37 @@ def sed(filename, sed_cmd, bak_ext="bak"):
|
|
|
print("problem with the regex; try a different pattern")
|
|
|
return (i, h)
|
|
|
|
|
|
-def _dir(d=''):
|
|
|
+def _dir(d='.'):
|
|
|
try:
|
|
|
- for g in os.listdir(d):
|
|
|
- s=os.stat(d+'/'+g)
|
|
|
- print("{}rwx all {:9d} {}".format('d' if (s[0] & 0x4000) else '-',s[6],g))
|
|
|
+ for f in os.listdir(d):
|
|
|
+ s=os.stat(d+'/'+f)
|
|
|
+ print("{}rwx all {:9d} {}".format('d' if (s[0] & 0x4000) else '-',s[6],f))
|
|
|
except:
|
|
|
print("not a valid directory")
|
|
|
s=os.statvfs('/')
|
|
|
- print("disk size:{:8d} KB disk free: {} KB\n".format(s[0]*s[2]//1024,s[0]*s[3]//1024))
|
|
|
+ print("disk size:{:8d} KB disk free: {} KB".format(s[0]*s[2]//1024,s[0]*s[3]//1024))
|
|
|
|
|
|
+'''-----cut here if you only need the functions-----'''
|
|
|
+def ext_cmd(a):
|
|
|
+ return
|
|
|
+if 'tf_extend.py' in os.listdir():
|
|
|
+ import tf_extend
|
|
|
+ ext_cmd=tf_extend.cmd
|
|
|
|
|
|
-'''-----cut here if you only need the above functions-----'''
|
|
|
def _help():
|
|
|
- print("simple shell v1.0")
|
|
|
+ print("==Simple shell v1.1")
|
|
|
print(" cp/copy <src-file> <dest-file>")
|
|
|
- print(" mv/move <src-file> <dest-file> rm/del <file>")
|
|
|
- print(" cd [<folder>] mkdir <folder> rmdir <folder>")
|
|
|
+ print(" mv/move <src-file> <dest-file> \t\trm/del <file>")
|
|
|
+ print(" cd [<folder>] mkdir <folder>\t\trmdir <folder>")
|
|
|
print(" dir/ls [<folder>]")
|
|
|
print(" cat/list [-n] [-l <n>,<m>] <file>")
|
|
|
print(" grep <pattern> <file>")
|
|
|
print(" sed <pattern> <file>")
|
|
|
- print(" where <pattern> is '[<n>,<m>] s/search/replace/' or '<n>[,<m>]d' or '<n>i<text>' or '<n>a<text' ")
|
|
|
- print("file names must NOT have embedded spaces options must be early on the command line")
|
|
|
- print("search patterns with spaces require single-quotes sed implements s/d/i/a/x/X")
|
|
|
- print("sed does not work across line boundaries sed s-patterns: non-/ delimiters are allowed")
|
|
|
+ print(" pattern is '<line-range><op><extra>' e.g'a/search/replace/', 'x!TODO:!', '43,49d', '8itext'")
|
|
|
+ print(" patterns with spaces require single-quotes sed ops are one of s/d/i/a/x/X")
|
|
|
+ print(" sed does not work across line boundaries sed s/x/X-patterns: non-/ delimiters are allowed")
|
|
|
+ print("file names must NOT have embedded spaces options must be early on the command line")
|
|
|
+ ext_cmd('help')
|
|
|
|
|
|
def parseQuotedArgs(st):
|
|
|
if st[0]=="'":
|
|
@@ -162,23 +174,23 @@ def parseQuotedArgs(st):
|
|
|
return st.split()[0]
|
|
|
|
|
|
def main():
|
|
|
- print("simple shell: cp/copy mv/move rm/del cat/list cd dir/ls mkdir rmdir grep sed help")
|
|
|
+ print("Simple shell: cp/copy mv/move rm/del cat/list cd dir/ls mkdir rmdir grep sed help")
|
|
|
while 1:
|
|
|
numbers=False
|
|
|
r=input(os.getcwd()+"$ ")
|
|
|
rp=r.split()
|
|
|
if not len(rp): continue
|
|
|
op=rp[0]
|
|
|
- if op=='dir' or op=='ls':
|
|
|
- _dir(rp[1] if len(rp)>1 else '')
|
|
|
- elif op=='cat' or op=='list':
|
|
|
+ if op in ('dir','ls'):
|
|
|
+ _dir(rp[1] if len(rp)>1 else '.')
|
|
|
+ elif op in ('cat','list'):
|
|
|
n=(" -n " in r) #print line-nums
|
|
|
- s,e=(1,1000000) #start/end
|
|
|
- g=re.search("\s+(-l\s*(\d+)([-,](\d+|\$)?)?)\s+",r[3:])
|
|
|
+ s,e=1,1000000 #start/end
|
|
|
+ g=re.search("\s(-l\s*(\d+)([-,](\d+|\$)?)?)\s+",r[3:])
|
|
|
if g:
|
|
|
s=e=int(g.group(2))
|
|
|
if g.group(3):
|
|
|
- e=int(g.group(4)) if g.group(4) and g.group(4).isdigit() else 1000000
|
|
|
+ e=int(g.group(4)) if g.group(4) and g.group(4).isdigit() else 1000000
|
|
|
cat(rp[-1],s,e,numbers=n)
|
|
|
elif op=='grep':
|
|
|
if len(rp)<3:
|
|
@@ -189,28 +201,32 @@ def main():
|
|
|
if len(rp)<3:
|
|
|
print("sed pattern filename")
|
|
|
continue
|
|
|
- lines, hits = sed(rp[-1],parseQuotedArgs(r[4:]))
|
|
|
- print("Lines processed: {} Lines modifed: {}".format(lines, hits))
|
|
|
+ r=sed(rp[-1],parseQuotedArgs(r[4:]))
|
|
|
+ if r:
|
|
|
+ print("Lines processed: {} Lines modifed: {}".format(*r))
|
|
|
elif op=='cd':
|
|
|
os.chdir(rp[1] if len(rp)>1 else '/')
|
|
|
elif op=='help':
|
|
|
_help()
|
|
|
+ ext_cmd(rp)
|
|
|
+ elif ext_cmd(rp):
|
|
|
+ pass
|
|
|
else:
|
|
|
try:
|
|
|
- if op=='cp' or op=='copy':
|
|
|
+ if op in ('cp','copy'):
|
|
|
cp(rp[1],rp[2])
|
|
|
elif op=='mkdir':
|
|
|
os.mkdir(rp[1])
|
|
|
elif op=='rmdir':
|
|
|
os.rmdir(rp[1])
|
|
|
- elif op=='mv' or op=='move':
|
|
|
+ elif op in('mv','move'):
|
|
|
os.rename(rp[1],rp[2])
|
|
|
- elif op=='rm' or op=='del':
|
|
|
+ elif op in('rm','del'):
|
|
|
os.remove(rp[1])
|
|
|
else:
|
|
|
print("command not implemented")
|
|
|
except IndexError:
|
|
|
- print("not enough argments; check syntax")
|
|
|
+ print("not enough argments; check syntax with 'help'")
|
|
|
except OSError:
|
|
|
print("file not found")
|
|
|
gc.collect()
|