45

I have a text file /etc/default/foo which contains one line:

FOO="/path/to/foo"

In my python script, I need to reference the variable FOO.

What is the simplest way to "source" the file /etc/default/foo into my python script, same as I would do in bash?

. /etc/default/foo
Martin Vegter
  • 136
  • 9
  • 32
  • 56

8 Answers8

36

Same answer as @jil however, that answer is specific to some historical version of Python.

In modern Python (3.x):

exec(open('filename').read())

replaces execfile('filename') from 2.x

Jason
  • 9,408
  • 5
  • 36
  • 36
Hack-R
  • 22,422
  • 14
  • 75
  • 131
  • 4
    Shoudn't one use a context manager (`with open…`) for this to ensure that the file handle gets cleaned up correctly? – Martin Ueding Jan 18 '21 at 15:13
18

You could use execfile:

execfile("/etc/default/foo")

But please be aware that this will evaluate the contents of the file as is into your program source. It is potential security hazard unless you can fully trust the source.

It also means that the file needs to be valid python syntax (your given example file is).

jil
  • 2,601
  • 12
  • 14
  • Of course, this is usually a really bad idea unless you have complete control over your input data (in which case, go for it, I guess). – larsks Apr 30 '16 at 19:18
  • 1
    It's not a bad answer (it *is* allowable) but the usual caveats should be there. – Harlin Apr 30 '16 at 19:28
  • 12
    Python3 version: `exec(open("/etc/default/foo").read())` http://stackoverflow.com/questions/436198/what-is-an-alternative-to-execfile-in-python-3 –  May 12 '17 at 04:39
9

Keep in mind that if you have a "text" file with this content that has a .py as the file extension, you can always do:

import mytextfile

print(mytestfile.FOO)

Of course, this assumes that the text file is syntactically correct as far as Python is concerned. On a project I worked on we did something similar to this. Turned some text files into Python files. Wacky but maybe worth consideration.

Harlin
  • 1,059
  • 14
  • 18
  • 1
    I think by wacky, you mean "generally accepted practice as seen in e.g. Django's settings.py mechanism for doing configuration"; this also has the benefits that you can do simple (or not so simple) calculations to build up strings etc. using the same python syntax as the rest of your program :-P – Foon Apr 30 '16 at 19:52
  • 1
    Importing the file will make it a separate module. The question specifically asks for a way to directly include the file (there _are_ valid use cases for such a request). – toolforger Dec 20 '22 at 23:00
7

Just to give a different approach, note that if your original file is setup as

export FOO=/path/to/foo

You can do source /etc/default/foo; python myprogram.py (or . /etc/default/foo; python myprogram.py) and within myprogram.py all the values that were exported in the sourced' file are visible in os.environ, e.g

import os
os.environ["FOO"] 
Foon
  • 6,148
  • 11
  • 40
  • 42
6

If you know for certain that it only contains VAR="QUOTED STRING" style variables, like this:

FOO="some value"

Then you can just do this:

>>> with open('foo.sysconfig') as fd:
...   exec(fd.read())

Which gets you:

>>> FOO
'some value'

(This is effectively the same thing as the execfile() solution suggested in the other answer.)

This method has substantial security implications; if instead of FOO="some value" your file contained:

os.system("rm -rf /")

Then you would be In Trouble.

Alternatively, you can do this:

>>> with open('foo.sysconfig') as fd:
...   settings = {var: shlex.split(value) for var, value in [line.split('=', 1) for line in fd]}

Which gets you a dictionary settings that has:

>>> settings
{'FOO': ['some value']}

That settings = {...} line is using a dictionary comprehension. You could accomplish the same thing in a few more lines with a for loop and so forth.

And of course if the file contains shell-style variable expansion like ${somevar:-value_if_not_set} then this isn't going to work (unless you write your very own shell style variable parser).

larsks
  • 277,717
  • 41
  • 399
  • 399
  • Why is `exec()` any more a security risk than `import`? If your file contains something like `os.system("rm -rf /")`, then aren't you doomed even if you used `import`? – Tim Mak Oct 09 '20 at 05:54
3

There are a couple ways to do this sort of thing.

  • You can indeed import the file as a module, as long as the data it contains corresponds to python's syntax. But either the file in question is a .py in the same directory as your script, either you're to use imp (or importlib, depending on your version) like here.

  • Another solution (that has my preference) can be to use a data format that any python library can parse (JSON comes to my mind as an example).

/etc/default/foo :

{"FOO":"path/to/foo"}

And in your python code :

import json

with open('/etc/default/foo') as file:
    data = json.load(file)
    FOO = data["FOO"]
    ## ...
    file.close()

This way, you don't risk to execute some uncertain code...

You have the choice, depending on what you prefer. If your data file is auto-generated by some script, it might be easier to keep a simple syntax like FOO="path/to/foo" and use imp.

Hope that it helps !

Community
  • 1
  • 1
1

The Solution

Here is my approach: parse the bash file myself and process only variable assignment lines such as:

FOO="/path/to/foo"

Here is the code:

import shlex

def parse_shell_var(line):
    """
    Parse such lines as:
        FOO="My variable foo"

    :return: a tuple of var name and var value, such as
        ('FOO', 'My variable foo')
    """
    return shlex.split(line, posix=True)[0].split('=', 1)

if __name__ == '__main__':
    with open('shell_vars.sh') as f:
        shell_vars = dict(parse_shell_var(line) for line in f if '=' in line)
    print(shell_vars)

How It Works

Take a look at this snippet:

        shell_vars = dict(parse_shell_var(line) for line in f if '=' in line)

This line iterates through the lines in the shell script, only process those lines that has the equal sign (not a fool-proof way to detect variable assignment, but the simplest). Next, run those lines into the function parse_shell_var which uses shlex.split to correctly handle the quotes (or the lack thereof). Finally, the pieces are assembled into a dictionary. The output of this script is:

{'MOO': '/dont/have/a/cow', 'FOO': 'my variable foo', 'BAR': 'My variable bar'}

Here is the contents of shell_vars.sh:

FOO='my variable foo'
BAR="My variable bar"
MOO=/dont/have/a/cow
echo $FOO

Discussion

This approach has a couple of advantages:

  • It does not execute the shell (either in bash or in Python), which avoids any side-effect
  • Consequently, it is safe to use, even if the origin of the shell script is unknown
  • It correctly handles values with or without quotes

This approach is not perfect, it has a few limitations:

  • The method of detecting variable assignment (by looking for the presence of the equal sign) is primitive and not accurate. There are ways to better detect these lines but that is the topic for another day
  • It does not correctly parse values which are built upon other variables or commands. That means, it will fail for lines such as:

    FOO=$BAR
    FOO=$(pwd)
    
Hai Vu
  • 37,849
  • 11
  • 66
  • 93
0

Based off the answer with exec(.read()), value = eval(.read()), it will only return the value. E.g.

1 + 1: 2
"Hello Word": "Hello World"
float(2) + 1: 3.0
Ryder R
  • 11
  • 4
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/33515933) – chrslg Dec 30 '22 at 14:27