最小打字命令行计算器 – tcsh vs bash
我喜欢有一个方便的命令行计算器.要求是:
>支持所有基本算术运算符:,–,/,*,^用于取幂,加上用于分组的括号. 由于tcsh支持别名位置参数,并且由于别名扩展优先于除历史扩展之外的所有其他扩展,因此可以直接在tcsh中实现接近我理想的东西. 我用过这个: alias C 'echo '''!*''' |tr -d ''',042-047''' |bc -l' 现在我可以通过最少的输入来完成以下内容: # the basic stuff: tcsh> C 1+2 3 # dollar signs,multiplication,exponentiation: tcsh> C $8 * 1.07^10 15.73721085831652257992 # parentheses,mixed spacing,zero power: tcsh> C ( 2+5 ) / 8 * 2^0 .87500000000000000000 # commas in numbers,no problem here either: tcsh> C 1,250.21 * 1.5 1875.315 正如你所看到的,没有必要引用任何东西来使所有这些工作. 现在出现了问题.试图在bash中做同样的事情,其中??不支持参数别名迫使我将计算器实现为shell函数并使用“$@”传递参数 function C () { echo "$@" | tr -d ', 42- 47' | bc -l; } 这会以各种方式打破,例如: # works: bash$ C 1+2 3 # works: bash$ C 1*2 2 # Spaces around '*' lead to file expansion with everything falling apart: bash$ C 1 * 2 (standard_in) 1: syntax error (standard_in) 1: illegal character: P (standard_in) 1: illegal character: S (standard_in) 1: syntax error ... # Non-leading parentheses seem to work: bash$ C 2*(2+1) 6 # but leading-parentheses don't: bash$ C (2+1)*2 bash: syntax error near unexpected token `2+1' 当然,在表达式周围添加引号可以解决这些问题,但这违背了原始要求. 我明白为什么事情会在bash中破裂.我不是在寻找解释.相反,我正在寻找一种不需要手动引用参数的解决方案.我对bash向导的问题是有没有办法让bash支持方便的最小打字计算器别名.不需要引用,像tcsh一样吗?这不可能吗?谢谢! 解决方法
至少可以使用’set -f’来阻止扩展*(跟随某人的
blog post:
alias C='set -f -B; Cf ' function Cf () { echo "$@" | tr -d ', 42- 47' | bc -l; set +f; }; 在别名中,在计算之前将其关闭,然后再重新打开 $C 2 * 3 6 我下载了bash源代码并且非常仔细地查看了.在运行任何命令或扩展别名之前,似乎在解析命令行期间直接发生括号错误.没有任何标志可以将其关闭. 这意味着,是时候携带重型武器了.在解析之前,使用readline从stdin读取命令行.因此,如果我们拦截对readline的调用,我们可以使用命令行做任何我们想做的事情. 不幸的是,bash与readline静态链接,因此无法直接拦截调用.但至少readline是一个全局符号,所以我们可以使用dlsym获取函数的地址,并且使用该地址我们可以在readline中插入任意指令. 如果在不同的bash版本之间更改readline,则直接修改readline会修剪错误,因此我们修改调用readline的函数,从而导致以下计划: >使用dlsym找到readline 用C语言写的amd64,我们得到: #include <string.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #ifndef __USE_GNU #define __USE_GNU #endif #ifndef __USE_MISC #define __USE_MISC #endif #include <dlfcn.h> #include <unistd.h> #include <sys/mman.h> #include <errno.h> //-----------Assembler helpers---------- #if (defined(x86_64) || defined(__x86_64__)) //assembler instructions to read rdp,which we need to read the stack #define MOV_EBP_OUT "mov %%rbp,%0" //size of a call instruction #define RELATIVE_CALL_INSTRUCTION_SIZE 5 #define IS64BIT (1) /* To replace a function with a new one,we use the push-ret trick,pushing the destination address on the stack and let ret jump "back" to it This has the advantage that we can set an additional return address in the same way,if the jump goes to a function This struct corresponds to the following assembler fragment: 68 ???? push <low_dword (address)> C7442404 ???? mov DWORD PTR [rsp+4],<high_dword (address) ) C3 ret */ typedef struct __attribute__((__packed__)) LongJump { char push; unsigned int destinationLow; unsigned int mov_dword_ptr_rsp4; unsigned int destinationHigh; char ret; // char nopFiller[16]; } LongJump; void makeLongJump(void* destination,LongJump* res) { res->push = 0x68; res->destinationLow = (uintptr_t)destination & 0xFFFFFFFF; res->mov_dword_ptr_rsp4 = 0x042444C7; res->destinationHigh = ((uintptr_t)(destination) >> 32) & 0xFFFFFFFF; res->ret = 0xC3; } //Macros to save and restore the rdi register,which is used to pass an address to readline (standard amd64 calling convention) typedef unsigned long SavedParameter; #define SAVE_PARAMETERS SavedParameter savedParameters; __asm__("mov %%rdi,%0": "=r"(savedParameters)); #define RESTORE_PARAMETERS __asm__("mov %0,%%rdi": : "r"(savedParameters)); #else #error only implmented for amd64... #endif //Simulates the effect of the POP instructions,popping from a passed "stack pointer" and returning the popped value static void * pop(void** stack){ void* temp = *(void**)(*stack); *stack += sizeof(void*); return temp; } //Disables the write protection of an address,so we can override it static int unprotect(void * POINTER){ const int PAGESIZE = sysconf(_SC_PAGE_SIZE);; if (mprotect((void*)(((uintptr_t)POINTER & ~(PAGESIZE-1))),PAGESIZE,PROT_READ | PROT_WRITE | PROT_EXEC)) { fprintf(stderr,"Failed to set permission on %pn",POINTER); return 1; } return 0; } //Debug stuff static void fprintfhex(FILE* f,void * hash,int len) { for (int i=0;i<len;i++) { if ((uintptr_t)hash % 8 == 0 && (uintptr_t)i % 8 == 0 && i ) fprintf(f," "); fprintf(f,"%.2x",((unsigned char*)(hash))[i]); } fprintf(f,"n"); } //--------------------------------------- //Address of the original readline function static char* (*real_readline)(const char*)=0; //The wrapper around readline we want to inject. //It replaces () with [],if the command line starts with "C " static char* readline_wrapper(const char* prompt){ if (!real_readline) return 0; char* result = real_readline(prompt); char* temp = result; while (*temp == ' ') temp++; if (temp[0] == 'C' && temp[1] == ' ') for (int len = strlen(temp),i=0;i<len;i++) if (temp[i] == '(') temp[i] = '['; else if (temp[i] == ')') temp[i] = ']'; return result; } //Backup of the changed readline part static unsigned char oldreadline[2*sizeof(LongJump)] = {0x90}; //A wrapper around the readline wrapper,needed on amd64 (see below) static LongJump* readline_wrapper_wrapper = 0; static void readline_initwrapper(){ SAVE_PARAMETERS if (readline_wrapper_wrapper) { fprintf(stderr,"ERROR!n"); return; } //restore readline memcpy(real_readline,oldreadline,2*sizeof(LongJump)); //find call in yy_readline_get void * frame; __asm__(MOV_EBP_OUT: "=r"(frame)); //current stackframe pop(&frame); //pop current stackframe (??) void * returnToFrame = frame; if (pop(&frame) != real_readline) { //now points to current return address fprintf(stderr,"Got %p instead of %p=readline,when searching callern",frame,real_readline); return; } void * caller = pop(&frame); //now points to the instruction following the call to readline caller -= RELATIVE_CALL_INSTRUCTION_SIZE; //now points to the call instruction //fprintf(stderr,"CALLER: %pn",caller); //caller should point to 0x00000000004229e1 <+145>: e8 4a e3 06 00 call 0x490d30 <readline> if (*(unsigned char*)caller != 0xE8) { fprintf(stderr,"Expected CALL,got: "); fprintfhex(stderr,caller,16); return; } if (unprotect(caller)) return; //We can now override caller to call an arbitrary function instead of readline. //However,the CALL instruction accepts only a 32 parameter,so the called function has to be in the same 32-bit address space //Solution: Allocate memory at an address close to that CALL instruction and put a long jump to our real function there void * hint = caller; readline_wrapper_wrapper = 0; do { if (readline_wrapper_wrapper) munmap(readline_wrapper_wrapper,2*sizeof(LongJump)); readline_wrapper_wrapper = mmap(hint,2*sizeof(LongJump),PROT_EXEC | PROT_READ | PROT_WRITE,MAP_ANONYMOUS|MAP_PRIVATE,-1,0); if (readline_wrapper_wrapper == MAP_FAILED) { fprintf(stderr,"mmap failed: %in",errno); return; } hint += 0x100000; } while ( IS64BIT && ( (uintptr_t)readline_wrapper_wrapper >= 0xFFFFFFFF + ((uintptr_t) caller) ) ); //repeat until we get an address really close to caller //fprintf(stderr,"X:%pn",readline_wrapper_wrapper); makeLongJump(readline_wrapper,readline_wrapper_wrapper); //Write the long jump in the newly allocated space //fprintfhex(stderr,readline_wrapper_wrapper,16); //fprintfhex(stderr,16); //patch caller to become call <readline_wrapper_wrapper> //called address is relative to address of CALL instruction *(uint32_t*)(caller+1) = (uint32_t) ((uintptr_t)readline_wrapper_wrapper - (uintptr_t)(caller + RELATIVE_CALL_INSTRUCTION_SIZE) ); //fprintfhex(stderr,16); *(void**)(returnToFrame) = readline_wrapper_wrapper; //change stack to jump to wrapper instead real_readline (or it would not work on the first entered command) RESTORE_PARAMETERS } static void _calc_init(void) __attribute__ ((constructor)); static void _calc_init(void){ if (!real_readline) { //Find readline real_readline = (char* (*)(const char*)) dlsym(RTLD_DEFAULT,"readline"); if (!real_readline) return; //fprintf(stdout,"loaded %pn",real_readline); //fprintf(stdout," => %xn",* ((int*) real_readline)); if (unprotect(real_readline)) { fprintf(stderr,"Failed to unprotect readlinen"); return; } memcpy(oldreadline,real_readline,2*sizeof(LongJump)); //backup readline's instructions //Replace readline with readline_initwrapper makeLongJump(real_readline,(LongJump*)real_readline); //add a push/ret long jump from readline to readline,to have readline's address on the stack in readline_initwrapper makeLongJump(readline_initwrapper,(LongJump*)((char*)real_readline + sizeof(LongJump) - 1)); //add a push/ret long jump from readline to readline_initwrapper,overriding the previous RET } } 这可以编译为拦截库: gcc -g -std=c99 -shared -fPIC -o calc.so -ldl calc.c 然后用bash加载: gdb --batch-silent -ex "attach $BASHPID" -ex 'print dlopen("calc.so",0x101)' 现在,当加载前面带有括号替换的别名时: alias C='set -f -B; Cf ' function Cf () { echo "$@" | tr -d ', 42- 47' | tr [ '(' | tr ] ')' | bc -l; set +f; }; 我们可以写: $ C 1 * 2 2 $ C 2*(2+1) 6 $ C (2+1)*2 6 如果我们从bc切换到qalculate,它会变得更好: alias C='set -f -B; Cf ' function Cf () { echo "$@" | tr -d ', 42- 47' | tr [ '(' | tr ] ')' | xargs qalc ; set +f; }; 然后我们可以这样做: $C e ^ (i * pi) e^(i * pi) = -1 $C 3 c 3 * speed_of_light = approx. 899.37737(km / ms) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |