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

195 lines, 85 significant
1from __future__ import print_function
2
3from _devbuild.gen.runtime_asdl import scope_e
4from _devbuild.gen.value_asdl import value, value_e
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 tagswitch, iteritems
12from osh import split
13
14import libc
15import posix_ as posix
16
17from typing import Dict, cast
18
19# This was derived from bash --norc -c 'argv "$COMP_WORDBREAKS".
20# Python overwrites this to something Python-specific in Modules/readline.c, so
21# we have to set it back!
22# Used in both core/competion.py and osh/state.py
23_READLINE_DELIMS = ' \t\n"\'><=;|&(:'
24
25
26def GetWorkingDir():
27 # type: () -> str
28 """Fallback for pwd and $PWD when there's no 'cd' and no inherited $PWD."""
29 try:
30 return posix.getcwd()
31 except (IOError, OSError) as e:
32 e_die("Can't determine working directory: %s" % pyutil.strerror(e))
33
34
35def InitDefaultVars(mem):
36 # type: (state.Mem) -> None
37
38 # These 3 are special, can't be changed
39 state.SetGlobalString(mem, 'UID', str(posix.getuid()))
40 state.SetGlobalString(mem, 'EUID', str(posix.geteuid()))
41 state.SetGlobalString(mem, 'PPID', str(posix.getppid()))
42
43 # For getopts builtin - meant to be read, not changed
44 state.SetGlobalString(mem, 'OPTIND', '1')
45
46 # These can be changed. Could go AFTER environment, e.g. in
47 # InitVarsAfterEnv().
48
49 # Default value; user may unset it.
50 # $ echo -n "$IFS" | python -c 'import sys;print repr(sys.stdin.read())'
51 # ' \t\n'
52 state.SetGlobalString(mem, 'IFS', split.DEFAULT_IFS)
53
54 state.SetGlobalString(mem, 'HOSTNAME', libc.gethostname())
55
56 # In bash, this looks like 'linux-gnu', 'linux-musl', etc. Scripts test
57 # for 'darwin' and 'freebsd' too. They generally don't like at 'gnu' or
58 # 'musl'. We don't have that info, so just make it 'linux'.
59 state.SetGlobalString(mem, 'OSTYPE', pyos.OsType())
60
61 # When xtrace_rich is off, this is just like '+ ', the shell default
62 state.SetGlobalString(mem, 'PS4',
63 '${SHX_indent}${SHX_punct}${SHX_pid_str} ')
64
65 # bash-completion uses this. Value copied from bash. It doesn't integrate
66 # with 'readline' yet.
67 state.SetGlobalString(mem, 'COMP_WORDBREAKS', _READLINE_DELIMS)
68
69 # TODO on $HOME: bash sets it if it's a login shell and not in POSIX mode!
70 # if (login_shell == 1 && posixly_correct == 0)
71 # set_home_var ();
72
73
74def CopyVarsFromEnv(exec_opts, environ, mem):
75 # type: (optview.Exec, Dict[str, str], state.Mem) -> None
76
77 # POSIX shell behavior: env vars become exported global vars
78 if not exec_opts.no_exported():
79 # This is the way dash and bash work -- at startup, they turn everything in
80 # 'environ' variable into shell variables. Bash has an export_env
81 # variable. Dash has a loop through environ in init.c
82 for n, v in iteritems(environ):
83 mem.SetNamed(location.LName(n),
84 value.Str(v),
85 scope_e.GlobalOnly,
86 flags=state.SetExport)
87
88 # YSH behavior: env vars go in ENV dict, not exported vars. Note that
89 # ysh:upgrade can have BOTH ENV and exported vars. It's OK if they're on
90 # at the same time.
91 if exec_opts.env_obj():
92 # This is for invoking bin/ysh
93 # If you run bin/osh, then exec_opts.env_obj() will be FALSE at this point.
94 # When you write shopt --set ysh:all or ysh:upgrade, then the shopt
95 # builtin will call MaybeInitEnvDict()
96 mem.MaybeInitEnvDict(environ)
97
98
99def InitVarsAfterEnv(mem):
100 # type: (state.Mem) -> None
101
102 # If PATH SHELLOPTS PWD are not in environ, then initialize them.
103 val = mem.GetValue('PATH')
104 if val.tag() == value_e.Undef:
105 # Setting PATH to these two dirs match what zsh and mksh do. bash and
106 # dash add {,/usr/,/usr/local}/{bin,sbin}
107 state.SetGlobalString(mem, 'PATH', '/bin:/usr/bin')
108
109 val = mem.GetValue('SHELLOPTS')
110 if val.tag() == value_e.Undef:
111 # Divergence: bash constructs a string here too, it doesn't just read it
112 state.SetGlobalString(mem, 'SHELLOPTS', '')
113 # It's readonly, even if it's not set
114 mem.SetNamed(location.LName('SHELLOPTS'),
115 None,
116 scope_e.GlobalOnly,
117 flags=state.SetReadOnly)
118 # NOTE: bash also has BASHOPTS
119
120 val = mem.GetValue('PWD')
121 if val.tag() == value_e.Undef:
122 state.SetGlobalString(mem, 'PWD', GetWorkingDir())
123 # It's EXPORTED, even if it's not set. bash and dash both do this:
124 # env -i -- dash -c env
125 mem.SetNamed(location.LName('PWD'),
126 None,
127 scope_e.GlobalOnly,
128 flags=state.SetExport)
129
130 # Set a MUTABLE GLOBAL that's SEPARATE from $PWD. It's used by the 'pwd'
131 # builtin, and it can't be modified by users.
132 val = mem.GetValue('PWD')
133 assert val.tag() == value_e.Str, val
134 pwd = cast(value.Str, val).s
135 mem.SetPwd(pwd)
136
137
138def InitBuiltins(mem, version_str):
139 # type: (state.Mem, str) -> None
140 """Initialize memory with shell defaults.
141
142 Other interpreters could have different builtin variables.
143 """
144 # TODO: REMOVE this legacy. ble.sh checks it!
145 mem.builtins['OIL_VERSION'] = value.Str(version_str)
146
147 mem.builtins['OILS_VERSION'] = value.Str(version_str)
148
149 # The source builtin understands '///' to mean "relative to embedded stdlib"
150 mem.builtins['LIB_OSH'] = value.Str('///osh')
151 mem.builtins['LIB_YSH'] = value.Str('///ysh')
152
153 # - C spells it NAN
154 # - JavaScript spells it NaN
155 # - Python 2 has float('nan'), while Python 3 has math.nan.
156 #
157 # - libc prints the strings 'nan' and 'inf'
158 # - Python 3 prints the strings 'nan' and 'inf'
159 # - JavaScript prints 'NaN' and 'Infinity', which is more stylized
160 mem.builtins['NAN'] = value.Float(pyutil.nan())
161 mem.builtins['INFINITY'] = value.Float(pyutil.infinity())
162
163
164def InitInteractive(mem, lang):
165 # type: (state.Mem, str) -> None
166 """Initialization that's only done in the interactive/headless shell."""
167
168 ps1_str = state.GetStringFromEnv(mem, 'PS1')
169 if ps1_str is None:
170 state.SetStringInEnv(mem, 'PS1', r'\s-\v\$ ')
171 else:
172 if lang == 'ysh':
173 state.SetStringInEnv(mem, 'PS1', 'ysh ' + ps1_str)
174
175 # Old logic:
176 if 0:
177 # PS1 is set, and it's YSH, then prepend 'ysh' to it to eliminate confusion
178 ps1_val = mem.GetValue('PS1')
179 with tagswitch(ps1_val) as case:
180 if case(value_e.Undef):
181 # Same default PS1 as bash
182 state.SetGlobalString(mem, 'PS1', r'\s-\v\$ ')
183
184 elif case(value_e.Str):
185 # Hack so we don't confuse osh and ysh, but we still respect the
186 # PS1.
187
188 # The user can disable this with
189 #
190 # func renderPrompt() {
191 # return ("${PS1@P}")
192 # }
193 if lang == 'ysh':
194 user_setting = cast(value.Str, ps1_val).s
195 state.SetGlobalString(mem, 'PS1', 'ysh ' + user_setting)