2013-02-19 55 views
5

मुझे कमांड लाइन कैलक्यूलेटर आसान होना पसंद है। आवश्यकताएँ हैं:न्यूनतम टाइपिंग कमांड लाइन कैलकुलेटर - टीसीएस बनाम बैश

  • समर्थन सभी बुनियादी अंकगणितीय ऑपरेटर: +, -, /, *,^घातांक के लिए, प्लस समूह के लिए कोष्ठक।
  • न्यूनतम टाइपिंग की आवश्यकता है, मैं इसके साथ एक प्रोग्राम को कॉल करने के लिए कॉल नहीं करना चाहता हूं, फिर इसे बाहर निकलने के लिए कह रहा हूं।
  • आदर्श रूप से अभिव्यक्ति के अलावा आदर्श रूप से केवल एक वर्ण और एक स्थान कमांड लाइन में दर्ज किया जाना चाहिए।
  • यह
  • बनें कैलकुलेटर में चिपका से पहले हर नंबर साफ करने के लिए होने के बारे में चिंता किए बिना पता होना चाहिए कि कैसे के लिए अल्पविराम और डॉलर (या अन्य मुद्रा प्रतीकों) की अनदेखी करने की संख्या में मुझे वेब से पेस्ट कॉपी करने के लिए/अनुमति देने के लिए 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 
    

    आप इन सभी काम करने के लिए कुछ भी उद्धृत करने के लिए कोई आवश्यकता नहीं है देख सकते हैं।

    अब समस्या आती है। जहां पैरामीटर उपनाम बलों मुझे समर्थन नहीं कर रहे एक खोल समारोह के रूप में कैलकुलेटर का उपयोग कर को लागू करने और "$ @"

    function C() { echo "[email protected]" | tr -d ', \042-\047' | bc -l; } 
    

    यह विभिन्न तरीकों से टूट जाता है पैरामीटर पास करने के bash, में भी ऐसा ही करने की कोशिश जैसे:

    # 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' 
    

    बेशक, अभिव्यक्ति के चारों ओर उद्धरण जोड़ना इन मुद्दों को हल करता है, लेकिन मूल आवश्यकताओं के खिलाफ है।

    मैं समझता हूं क्यों चीजें तोड़ने में टूट जाती हैं। मैं स्पष्टीकरण की तलाश नहीं कर रहा हूं। इसके बजाय, मैं समाधान ढूंढ रहा हूं जिसके लिए तर्कों को मैन्युअल रूप से उद्धृत करने की आवश्यकता नहीं है। जादूगरों को झुकाव के लिए मेरा सवाल है कि कम से कम टाइपिंग कैलकुलेटर उपनाम को बैश समर्थन करने का कोई तरीका है। उद्धरण की आवश्यकता नहीं है, जैसे टीसीएसएच करता है? क्या यह असंभव है? धन्यवाद!

  • +0

    आपकी पहली दो आवश्यकताएं संघर्ष करती हैं। यदि आप न्यूनतम कीस्ट्रोक चाहते हैं तो आप रिवर्स पॉलिश चाहते हैं, इसलिए आपको समूहबद्ध करने के लिए भी ब्रांड्स की आवश्यकता नहीं है। –

    +0

    पर्याप्त मेला।मेरा मतलब प्राकृतिक मानव नोटेशन को ध्यान में रखते हुए न्यूनतम था :) – arielf

    +0

    इस पर पुनर्विचार करना: रिवर्स-पॉलिश को दो नंबरों के बीच 'पुश/एंटर' ऑपरेटर की आवश्यकता होगी ताकि इन्फिक्स 1 + 2 (3 वर्ण) वास्तव में रिवर्स-पॉलिश की तुलना में कम इनपुट टाइपिंग की आवश्यकता हो: 1 2+ (4 वर्ण) - बस एक उदाहरण के रूप में। – arielf

    उत्तर

    2

    कम से कम * के विस्तार को रोकने का उपयोग कर (निम्नलिखित '-f सेट' संभव है किसी के blog post:

    alias C='set -f -B; Cf ' 
    function Cf() { echo "[email protected]" | tr -d ', \042-\047' | bc -l; set +f; }; 
    

    यह बाद में अन्य नाम में बंद किया जा रहा, गणना से पहले, और पीठ पर

    $ C 2 * 3 
    6 
    

    मैंने बैश स्रोत डाउनलोड किए और बहुत बारीकी से देखा। ऐसा लगता है कि किसी भी कमांड को चलाने से पहले, कमांड लाइन के पार्सिंग के दौरान सीधे कोष्ठक त्रुटि होती है आर उपनाम का विस्तार किया गया है। और बिना किसी ध्वज के इसे बंद करने के लिए। तो इसे एक बैश स्क्रिप्ट से करना असंभव होगा।

    इसका मतलब है, यह भारी हथियार लाने का समय है। कमांड लाइन को पार्स करने से पहले रीडलाइन का उपयोग करके stdin से पढ़ा जाता है। इसलिए, अगर हम कॉललाइन को कॉल को अवरुद्ध करते हैं, तो हम कमांड लाइन के साथ जो भी चाहते हैं हम कर सकते हैं।

    दुर्भाग्यवश बैश को रीडलाइन के खिलाफ स्थिर रूप से लिंक किया गया है, इसलिए कॉल को सीधे अवरुद्ध नहीं किया जा सकता है। लेकिन कम से कम रीडलाइन एक वैश्विक प्रतीक है, इसलिए हम dlsym का उपयोग करके फ़ंक्शन का पता प्राप्त कर सकते हैं, और उस पते के साथ हम रीडलाइन में मनमानी निर्देश डाल सकते हैं।

    संशोधित करना ReadLine सीधे, त्रुटियों के लिए छँटाई है अगर ReadLine अलग बैश संस्करण के बीच परिवर्तित हो जाता है, तो हम समारोह बुला ReadLine संशोधित करने, निम्नलिखित योजना को प्रेरित किया:

    1. dlsym
    2. बदलें ReadLine साथ ReadLine जानें हमारे अपने समारोह में अपनी पहली कॉल पर समारोह बुला ReadLine (yy_readline_get) का पता लगाने की मौजूदा ढेर का उपयोग करता है और फिर उस के साथ मूल ReadLine पुनर्स्थापित करता
    3. संशोधित yy_readline_get हमारे आवरण समारोह
    4. डब्ल्यू कॉल करने के लिए आवरण समारोह ithin:, गैर समस्याग्रस्त प्रतीकों के साथ कोष्ठक की जगह यदि इनपुट 'सी'

    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 %p\n", 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 caller\n", 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: %p\n", 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: %i\n", 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:%p\n", 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, caller, 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, caller, 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 %p\n", real_readline); 
        //fprintf(stdout, " => %x\n", * ((int*) real_readline)); 
    
        if (unprotect(real_readline)) { fprintf(stderr, "Failed to unprotect readline\n"); 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 
    
        } 
    } 
    

    यह एक इंटरसेप्टिंग के लिए संकलित किया जा सकता साथ पुस्तकालय:

    gdb --batch-silent -ex "attach $BASHPID" -ex 'print dlopen("calc.so", 0x101)' 
    
    :

    gcc -g -std=c99 -shared -fPIC -o calc.so -ldl calc.c 
    

    और फिर साथ बैश में लोड

    अब, जब पिछले उर्फ ​​कोष्टक प्रतिस्थापन के साथ बढ़ाया लोड किया जाता है:

    alias C='set -f -B; Cf ' 
    function Cf() { echo "[email protected]" | tr -d ', \042-\047' | tr [ '(' | tr ] ')' | bc -l; set +f; }; 
    

    हम लिख सकते हैं:

    $ C 1 * 2 
        2 
    $ C 2*(2+1) 
        6 
    $ C (2+1)*2 
        6 
    

    भी अच्छी बात यह हो जाता है अगर हम ईसा पूर्व से qalculate करने के लिए स्विच:

    alias C='set -f -B; Cf ' 
    function Cf() { echo "[email protected]" | tr -d ', \042-\047' | 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) 
    
    +0

    कूल। इस टिप के लिए धन्यवाद! यह मेरे प्रारंभिक समाधान पर एक बड़ा सुधार है। मदद की सराहना करें। – arielf

    +0

    एक और प्रश्नोत्तरी: यह संशोधन अन्य अभिव्यक्तियों को तोड़ता है: 'सी 1 * 2' 0 देता है, 'सी 1 + 2' देता है 2. – arielf

    +0

    अजीब, यह मेरे लिए ठीक काम करता है। – BeniBela

    3

    आप टाइप करने के लिए तैयार हैं तो Cदर्ज बजाय C की अंतरिक्ष, आकाश सीमा है। C कमांड आपके इच्छित रूप में इनपुट ले सकता है, जो शेल सिंटैक्स से असंबंधित है।

    C() { 
        local line 
        read -p "Arithmetic: " -e line 
        echo "$line" | tr -d \"-\', | bc -l 
    } 
    

    zsh में:

    function C { 
        local line= 
        vared -p "Arithmetic: " line 
        echo $line | tr -d \"-\', | bc -l 
    } 
    

    zsh में, आप noglob modifier के साथ एक विशिष्ट आदेश के तर्कों के लिए ग्लोबिंग बंद कर सकते हैं। यह आमतौर पर उपनाम में छिपा हुआ है। यह *^() को शाब्दिक रूप से व्याख्या से शुरू करता है, लेकिन उद्धरण या $ नहीं है।

    quickie_arithmetic() { 
        echo "$*" | tr -d \"-\', | bc -l 
    } 
    alias C='noglob quickie_arithmetic' 
    
    +0

    धन्यवाद! यह एक सुंदर चालाक मोड़ है। यह अंधेरे में स्पष्ट है, लेकिन जब मैंने मूल प्रश्न पूछा तो मैंने इसे पूरी तरह से याद किया। – arielf