2

I have a function that checks if a file exists, it returns 'True'/'False', right now I'm 'converting' it to bool with eval(), however I don't think this is the smartest solution, but I'm not sure how else to do it without unnecessary ifs,

>>> foo = 'False'
>>> type(eval(foo))
<class 'bool'>
>>> type(foo)
<class 'str'>

For example, I'm running this expression, on ssh connected machine

"test -e {0} && echo True || echo False".format(self.repo)

like this, and my result is going to be string.

def execute(command):
    (_, stdOut, _) = ssh.exec_command(command)
    output = stdOut.read()
    return output.decode('utf-8')

Is there any other way to achieve this?

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Simeon Aleksov
  • 1,275
  • 1
  • 13
  • 25
  • 2
    There's no reason for the shell line to echo anything. `test -e {0}` will have an *exit status* of 0 if the expression is true, nonzero (likely 1) otherwise. Test *that*. – chepner Jan 02 '20 at 22:32
  • 1
    No, the exit status of `ssh` will be the exit status of the command it runs. Whatever you are using to run `ssh` will provide someway to access the integer exit status. (For example, `subprocess.run(['ssh', some_host, f'test -e "{some_file}"']).returncode == 0` (ignoring the issue of ensuring that `some_file` is properly escaped for inclusion in a shell command).) – chepner Jan 02 '20 at 22:35

4 Answers4

5

You can use ast.literal_eval(). This is safer than eval() because it only evaluates literals, not arbitrary expressions.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • This is great, I think I'll be using this, I'll test it out first. – Simeon Aleksov Jan 02 '20 at 22:42
  • 2
    Though this answers the question you asked, you should not be using that code in the first place. There's no need to convert the exit status to a string when you can get the exit status directly. – chepner Jan 02 '20 at 22:48
  • True, although the exit status will still have to be converted: 0 => true, non-zero => false, which is the opposite of Python's truthiness. – Barmar Jan 02 '20 at 22:51
3

A filename should always be quoted before including it in a context where it might be parsed as code.

Here, we're using the technique introduced in How can you get the SSH return code using Paramiko? to retrieve exit status directly from the SSH channel, with no need to parse any string passed over stdout.

try:
  from pipes import quote  # Python 2.x
except ImportError:
  from shlex import quote  # Python 3.x

def test_remote_existance(filename):
    # assuming that "ssh" is a paramiko SSHClient object
    command = 'test -e {0} </dev/null >/dev/null 2>&1'.format(quote(remote_file))
    chan = ssh.get_transport().open_session()
    chan.exec_command(command)
    return chan.recv_exit_status() == 0
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Frankly, I consider it a pretty serious design flaw in ssh that it only passes a single string as the remote command to run -- if it passed a full argv array, `quote()` would be unnecessary in its invocation. Alas, we're several decades too late to fix that error. – Charles Duffy Jan 02 '20 at 23:01
  • Your code will correctly capture the exit code for this specific command that cannot have any output. But had the command produced an output, you cannot directly use recv_exit_status, as the code may deadlock. You have to consume the command output, while waiting for the command to finish. See [Paramiko ssh die/hang with big output](https://stackoverflow.com/q/31625788/850848). – Also for this specific task, it's better to use SFTP, instead of shell commands. See [my answer](https://stackoverflow.com/q/59570800/850848#59608563). As a side effect, this would solve all quoting problems. – Martin Prikryl Aug 20 '20 at 05:40
1

To test a file existence over SSH, use the standard API – SFTP, instead of running shell commands.

With Paramiko you can do that by:

sftp = ssh.open_sftp()
try:
    sftp.stat(path)
    print("File exists")
except IOError:
    print("File does not exist or cannot be accessed")
Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
0

It is best practice in python to return the operation that determines the boolean in python rather than doing something like):

if something:
    return True
else:
    return False

An example of this using your file checker (this doesn't need to be wrapped in a function but for example's sake:

import os

def check_file(infile):
    return os.path.isfile(infile)

print(type(check_file('fun.py'))) # is true # <class 'bool'>
print(type(check_file('nonexistent.txt'))) # is false # <class 'bool'>
d_kennetz
  • 5,219
  • 5
  • 21
  • 44
  • I'm aware of this, however in my case I need to run this on another machine, which I'm connected with ssh, so I would need to run `check_file()`, and it will return string again. – Simeon Aleksov Jan 02 '20 at 22:37