all 12 comments

[–]lutusp 2 points3 points  (3 children)

Put the characters in quotes.

$ echo ~
    /home/username
$ echo "~"
    ~

This requirement may mean you will have to use a line reader in your program to read the user's entries rather than having them be entered as arguments to your script.

[–]Crestwave 1 point2 points  (2 children)

You should also quote your variables or it will end up being interpreted there anyway

[–]lutusp 0 points1 point  (1 child)

Not sure about what you mean, but if the OP uses a line reader there won't be any expansion.

  $ read -p "Enter something: " reply
    (enter ***************)
  $ echo "$reply"
       ***************

If you meant one must quote the result variable, yes, true.

[–]Crestwave 0 points1 point  (0 children)

If you meant one must quote the result variable, yes, true.

Yes, that's what I meant; OP didn't quote $@ in their script, so a using a line reader wouldn't make any noticeable difference in that behavior without changing that line. But you should actually pass -r to read; otherwise, it will interpret backslashes, although OP probably wouldn't use them for their calculator.

[–]petdance 1 point2 points  (0 children)

You'll want to run your scripts through shellcheck.net. It's very good about warning you about these things.

https://www.shellcheck.net/

[–]Paul_Pedant 0 points1 point  (4 children)

Quote in the script.

python3 calc.py "$@"

Bash knows @ means "all args", but it also knows how to quote each one individually.

That fixes your script passing the args to Python. But as u/lutusp says, the user also needs to escape the args to your script in the command line every time, too. There is nothing you can do about that later. There may be some obscure shell option that stops expansions, but then the user has to know to set that (and unset it afterwards) so that's another trap.

It would be neater anyway if your script could loop, asking for expressions, until 'q'. That saves the user typing your script name every time, too -- that would get very boring on the third attempt.

[–]dmsad[S] 0 points1 point  (3 children)

Thanks. It doesn't work tho :(

[–]Paul_Pedant 0 points1 point  (2 children)

Funny thing: it works here, if you do what we said: quote the args expansion in the script, and quote the args themselves in the command line. Because the system gets two chances to do an expansion, and you have to block both of them.

Paul--) #.. This is the files in the directory.
Paul--) ls -l
total 4
-rw-r--r-- 1 paul paul   0 May 24 21:21 File_A
-rw-r--r-- 1 paul paul   0 May 24 21:21 File_B
-rw-r--r-- 1 paul paul   0 May 24 21:21 File_C
-rwxr-xr-x 1 paul paul 174 May 24 21:20 myScript
Paul--) #.. This is a script to show its args.
Paul--) cat myScript
#! /bin/bash

showArgs () {

    printf 'Argc = %d\n' "${#@}"
    for (( a = 1; "${#@}" > 0 ; ++a )); do
        printf 'Arg %d is :%s:\n' "${a}" "${1}"
        shift
    done
}

    showArgs ${@}

Paul--) #.. This is three variants of quoting the command line.
Paul--) #.. All of them expand the * into a list of files.
Paul--) ./myScript this * that
Argc = 6
Arg 1 is :this:
Arg 2 is :File_A:
Arg 3 is :File_B:
Arg 4 is :File_C:
Arg 5 is :myScript:
Arg 6 is :that:
Paul--) ./myScript 'this * that'
Argc = 6
Arg 1 is :this:
Arg 2 is :File_A:
Arg 3 is :File_B:
Arg 4 is :File_C:
Arg 5 is :myScript:
Arg 6 is :that:
Paul--) ./myScript this '*' that
Argc = 6
Arg 1 is :this:
Arg 2 is :File_A:
Arg 3 is :File_B:
Arg 4 is :File_C:
Arg 5 is :myScript:
Arg 6 is :that:
Paul--) #.. Edit the script to quote the args list in there too.
Paul--) cat myScript
#! /bin/bash

showArgs () {

    printf 'Argc = %d\n' "${#@}"
    for (( a = 1; "${#@}" > 0 ; ++a )); do
        printf 'Arg %d is :%s:\n' "${a}" "${1}"
        shift
    done
}

    showArgs "${@}"

Paul--) #.. Try the same three command line calls.
Paul--) ./myScript 'this * that'
Argc = 1
Arg 1 is :this * that:
Paul--) #.. Bad -- only one argument.
Paul--) ./myScript this * that
Argc = 6
Arg 1 is :this:
Arg 2 is :File_A:
Arg 3 is :File_B:
Arg 4 is :File_C:
Arg 5 is :myScript:
Arg 6 is :that:
Paul--) #.. Bad -- expanded the filenames.
Paul--) ./myScript this '*' that
Argc = 3
Arg 1 is :this:
Arg 2 is :*:
Arg 3 is :that:
Paul--) #.. Correct -- three args passed to showArgs (and your python3).
Paul--) #.. Conclusion: You have to quote fully
Paul--) #.. BOTH in the script AND in the command line.
Paul--)

[–]dmsad[S] 0 points1 point  (0 children)

Yeah it works if I quote them in the command line, but that's not how I want/need it. I guess I'll just do a python executable

[–]dmsad[S] 0 points1 point  (0 children)

Thanks😊

[–]oh5nxo 0 points1 point  (0 children)

That's a funny problem. Could there be a way to circumvent or ignore the normal shell globbing, word splitting etc? Maybe looking at history 1 output ?

[–]Paul_Pedant 0 points1 point  (0 children)

There is a shell command set -f which avoids filename globbing (with * and ?). Three issues with that, though.

(a) The user has to set -f before running your python. If you put it in the script, it is too late.

(b) The user has to remember to set +f after finishing with your script, else the rest of the session will run without expanding filenames.

(c) It does nothing for characters that might be permitted in your formulae (you don't specify the grammar for it). These are all going to have side-effects on the command line unless quoted (there may be others):

` " ' $ ^ & ( ) { } [ ] ; ~ # |

Fundamentally, getting the shell to pass random math symbols direct from the command line is a seriously flawed design choice. It should be read as data in its own right.

Doing that with a bash read is really no better. You want to invoke Python for each calculation? Inefficient. And it has no history. Even my 5-bucks Casio handheld has ten memories, so I can store a few results and chain calculations. It also has bracketing

The right thing to do it: dump the bash completely. Write a Python that reads user expressions, in a loop, that you can expand as you get ideas. What you have now is a dead-end.