OILS / core / sh_init.py View on Github | oilshell.org

310 lines, 128 significant
1from __future__ import print_function
2
3from _devbuild.gen.runtime_asdl import scope_e
4from _devbuild.gen.value_asdl import value, value_e, value_t
5from core.error import e_die
6from core import pyos
7from core import pyutil
8from core import optview
9from core import state
10from frontend import location
11from mycpp.mylib import iteritems, log
12from osh import split
13from pylib import os_path
14
15import libc
16import posix_ as posix
17
18from typing import Dict, Optional, cast, TYPE_CHECKING
19if TYPE_CHECKING:
20 from _devbuild.gen import arg_types
21
22_ = log
23
24
25class EnvConfig(object):
26 """Define a string config var read from the environment.
27
28 And it's default.
29
30 In OSH, it will appear as $PS1 or $PATH or $PWD. You can't see the
31 default.
32
33 In YSH, it will appear as ENV.PS1 and __default__.PS1. I guess __default__
34 can be a Dict or Obj.
35
36 Usage:
37
38 env_config.Define('PS1', r'\\s-\\v')
39
40 # YSH: set ENV.PS1
41 # OSH: set PS1
42 env_config.InitFromEnv('PS1')
43
44 # YSH - get from ENV or __default__
45 env_config.Get('PS1')
46
47 # Custom logic for PWD
48 if not env_config.Exists('PWD'):
49 pass
50
51 More features:
52
53 - On-demand BASHPID
54 - io.thisPid() - is BASHPID
55 - io.pid() - is $$
56 - Init-once UID EUID PPID
57 - maybe this should be a separate Funcs class?
58 - io.uid() io.euid() io.ppid()
59 """
60
61 def __init__(self, mem, defaults):
62 # type: (state.Mem, Dict[str, value_t]) -> None
63
64 # mutates env_dict
65 self.mem = mem
66 self.exec_opts = mem.exec_opts
67 self.defaults = defaults
68
69 def Define(self, var_name, default_s):
70 # type: (str, str) -> None
71 """
72 """
73
74 def GetVal(self, var_name):
75 # type: (str) -> value_t
76 """
77 YSH: Look at ENV.PATH, and then __defaults__.PATH
78 OSH: Look at $PATH
79 """
80 if self.mem.exec_opts.env_obj(): # e.g. $[ENV.PATH]
81
82 val = self.mem.env_dict.get(var_name)
83 if val is None:
84 val = self.defaults.get(var_name)
85
86 if val is None:
87 return value.Undef
88
89 #log('**ENV obj val = %s', val)
90
91 else: # e.g. $PATH
92 val = self.mem.GetValue(var_name)
93
94 return val
95
96 def Get(self, var_name):
97 # type: (str) -> Optional[str]
98 """
99 Like GetVal(), but returns a strin, or None
100 """
101 val = self.GetVal(var_name)
102 if val.tag() != value_e.Str:
103 return None
104 return cast(value.Str, val).s
105
106 def SetDefault(self, var_name, s):
107 # type: (str, str) -> None
108 """
109 OSH: Set HISTFILE var, which is read by GetVal()
110 YSH: Set __defaults__.YSH_HISTFILE, which is also read by GetVal()
111 """
112 if self.mem.exec_opts.env_obj(): # e.g. $[ENV.PATH]
113 self.mem.defaults[var_name] = value.Str(s)
114 else:
115 state.SetGlobalString(self.mem, var_name, s)
116
117
118class ShellFiles(object):
119
120 def __init__(self, lang, home_dir, mem, flag):
121 # type: (str, str, state.Mem, arg_types.main) -> None
122 assert lang in ('osh', 'ysh'), lang
123 self.lang = lang
124 self.home_dir = home_dir
125 self.mem = mem
126 self.flag = flag
127
128 self.init_done = False
129
130 def HistVar(self):
131 # type: () -> str
132 return 'HISTFILE' if self.lang == 'osh' else 'YSH_HISTFILE'
133
134 def DefaultHistoryFile(self):
135 # type: () -> str
136 return os_path.join(self.home_dir,
137 '.local/share/oils/%s_history' % self.lang)
138
139 def HistoryFile(self):
140 # type: () -> Optional[str]
141 assert self.init_done
142
143 return self.mem.env_config.Get(self.HistVar())
144
145
146def GetWorkingDir():
147 # type: () -> str
148 """Fallback for pwd and $PWD when there's no 'cd' and no inherited $PWD."""
149 try:
150 return posix.getcwd()
151 except (IOError, OSError) as e:
152 e_die("Can't determine working directory: %s" % pyutil.strerror(e))
153
154
155# This was derived from bash --norc -c 'argv "$COMP_WORDBREAKS".
156# Python overwrites this to something Python-specific in Modules/readline.c, so
157# we have to set it back!
158# Used in both core/competion.py and osh/state.py
159_READLINE_DELIMS = ' \t\n"\'><=;|&(:'
160
161
162def InitDefaultVars(mem):
163 # type: (state.Mem) -> None
164
165 # These 3 are special, can't be changed
166 state.SetGlobalString(mem, 'UID', str(posix.getuid()))
167 state.SetGlobalString(mem, 'EUID', str(posix.geteuid()))
168 state.SetGlobalString(mem, 'PPID', str(posix.getppid()))
169
170 # For getopts builtin - meant to be read, not changed
171 state.SetGlobalString(mem, 'OPTIND', '1')
172
173 # These can be changed. Could go AFTER environment, e.g. in
174 # InitVarsAfterEnv().
175
176 # Default value; user may unset it.
177 # $ echo -n "$IFS" | python -c 'import sys;print repr(sys.stdin.read())'
178 # ' \t\n'
179 state.SetGlobalString(mem, 'IFS', split.DEFAULT_IFS)
180
181 state.SetGlobalString(mem, 'HOSTNAME', libc.gethostname())
182
183 # In bash, this looks like 'linux-gnu', 'linux-musl', etc. Scripts test
184 # for 'darwin' and 'freebsd' too. They generally don't like at 'gnu' or
185 # 'musl'. We don't have that info, so just make it 'linux'.
186 state.SetGlobalString(mem, 'OSTYPE', pyos.OsType())
187
188 # When xtrace_rich is off, this is just like '+ ', the shell default
189 state.SetGlobalString(mem, 'PS4',
190 '${SHX_indent}${SHX_punct}${SHX_pid_str} ')
191
192 # bash-completion uses this. Value copied from bash. It doesn't integrate
193 # with 'readline' yet.
194 state.SetGlobalString(mem, 'COMP_WORDBREAKS', _READLINE_DELIMS)
195
196 # TODO on $HOME: bash sets it if it's a login shell and not in POSIX mode!
197 # if (login_shell == 1 && posixly_correct == 0)
198 # set_home_var ();
199
200
201def CopyVarsFromEnv(exec_opts, environ, mem):
202 # type: (optview.Exec, Dict[str, str], state.Mem) -> None
203
204 # POSIX shell behavior: env vars become exported global vars
205 if not exec_opts.no_exported():
206 # This is the way dash and bash work -- at startup, they turn everything in
207 # 'environ' variable into shell variables. Bash has an export_env
208 # variable. Dash has a loop through environ in init.c
209 for n, v in iteritems(environ):
210 mem.SetNamed(location.LName(n),
211 value.Str(v),
212 scope_e.GlobalOnly,
213 flags=state.SetExport)
214
215 # YSH behavior: env vars go in ENV dict, not exported vars. Note that
216 # ysh:upgrade can have BOTH ENV and exported vars. It's OK if they're on
217 # at the same time.
218 if exec_opts.env_obj():
219 # This is for invoking bin/ysh
220 # If you run bin/osh, then exec_opts.env_obj() will be FALSE at this point.
221 # When you write shopt --set ysh:all or ysh:upgrade, then the shopt
222 # builtin will call MaybeInitEnvDict()
223 mem.MaybeInitEnvDict(environ)
224
225
226def InitVarsAfterEnv(mem):
227 # type: (state.Mem) -> None
228
229 # If PATH SHELLOPTS PWD are not in environ, then initialize them.
230 s = mem.env_config.Get('PATH')
231 if s is None:
232 # Setting PATH to these two dirs match what zsh and mksh do. bash and
233 # dash add {,/usr/,/usr/local}/{bin,sbin}
234 mem.env_config.SetDefault('PATH', '/bin:/usr/bin')
235
236 val = mem.GetValue('SHELLOPTS')
237 if val.tag() == value_e.Undef:
238 # Divergence: bash constructs a string here too, it doesn't just read it
239 state.SetGlobalString(mem, 'SHELLOPTS', '')
240 # It's readonly, even if it's not set
241 mem.SetNamed(location.LName('SHELLOPTS'),
242 None,
243 scope_e.GlobalOnly,
244 flags=state.SetReadOnly)
245 # NOTE: bash also has BASHOPTS
246
247 val = mem.GetValue('PWD')
248 if val.tag() == value_e.Undef:
249 state.SetGlobalString(mem, 'PWD', GetWorkingDir())
250 # It's EXPORTED, even if it's not set. bash and dash both do this:
251 # env -i -- dash -c env
252 mem.SetNamed(location.LName('PWD'),
253 None,
254 scope_e.GlobalOnly,
255 flags=state.SetExport)
256
257 # Set a MUTABLE GLOBAL that's SEPARATE from $PWD. It's used by the 'pwd'
258 # builtin, and it can't be modified by users.
259 val = mem.GetValue('PWD')
260 assert val.tag() == value_e.Str, val
261 pwd = cast(value.Str, val).s
262 mem.SetPwd(pwd)
263
264
265def InitInteractive(mem, sh_files, lang):
266 # type: (state.Mem, ShellFiles, str) -> None
267 """Initialization that's only done in the interactive/headless shell."""
268
269 ps1_str = mem.env_config.Get('PS1')
270 if ps1_str is None:
271 mem.env_config.SetDefault('PS1', r'\s-\v\$ ')
272 else:
273 if lang == 'ysh':
274 # If this is bin/ysh, and we got a plain PS1, then prepend 'ysh ' to PS1
275 mem.env_dict['PS1'] = value.Str('ysh ' + ps1_str)
276
277 hist_var = sh_files.HistVar()
278 hist_str = mem.env_config.Get(hist_var)
279 if hist_str is None:
280 mem.env_config.SetDefault(hist_var, sh_files.DefaultHistoryFile())
281
282 sh_files.init_done = True # sanity check before using sh_files
283
284
285def InitBuiltins(mem, version_str, defaults):
286 # type: (state.Mem, str, Dict[str, value_t]) -> None
287 """Initialize memory with shell defaults.
288
289 Other interpreters could have different builtin variables.
290 """
291 # TODO: REMOVE this legacy. ble.sh checks it!
292 mem.builtins['OIL_VERSION'] = value.Str(version_str)
293
294 mem.builtins['OILS_VERSION'] = value.Str(version_str)
295
296 mem.builtins['__defaults__'] = value.Dict(defaults)
297
298 # The source builtin understands '///' to mean "relative to embedded stdlib"
299 mem.builtins['LIB_OSH'] = value.Str('///osh')
300 mem.builtins['LIB_YSH'] = value.Str('///ysh')
301
302 # - C spells it NAN
303 # - JavaScript spells it NaN
304 # - Python 2 has float('nan'), while Python 3 has math.nan.
305 #
306 # - libc prints the strings 'nan' and 'inf'
307 # - Python 3 prints the strings 'nan' and 'inf'
308 # - JavaScript prints 'NaN' and 'Infinity', which is more stylized
309 mem.builtins['NAN'] = value.Float(pyutil.nan())
310 mem.builtins['INFINITY'] = value.Float(pyutil.infinity())