about to implement readline myself

This commit is contained in:
2025-05-15 00:53:39 -04:00
parent c9414c8ce3
commit 94e22d68f3
9 changed files with 291 additions and 118 deletions

View File

@@ -5,8 +5,8 @@ use std::str::{Chars, FromStr};
use glob::Pattern;
use regex::Regex;
use crate::state::{read_vars, write_meta, write_vars, LogTab};
use crate::procio::{IoBuf, IoFrame, IoMode};
use crate::state::{read_jobs, read_vars, write_jobs, write_meta, write_vars, LogTab};
use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
use crate::prelude::*;
use crate::parse::{Redir, RedirType};
use crate::parse::execute::exec_input;
@@ -23,6 +23,10 @@ pub const SNG_QUOTE: char = '\u{fdd2}';
pub const TILDE_SUB: char = '\u{fdd3}';
/// Subshell marker
pub const SUBSH: char = '\u{fdd4}';
/// Input process sub marker
pub const PROC_SUB_IN: char = '\u{fdd5}';
/// Output process sub marker
pub const PROC_SUB_OUT: char = '\u{fdd6}';
impl Tk {
/// Create a new expanded token
@@ -105,6 +109,28 @@ pub fn expand_raw(chars: &mut Peekable<Chars<'_>>) -> ShResult<String> {
let home = env::var("HOME").unwrap_or_default();
result.push_str(&home);
}
PROC_SUB_OUT =>{
let mut inner = String::new();
while let Some(ch) = chars.next() {
match ch {
PROC_SUB_OUT => break,
_ => inner.push(ch)
}
}
let fd_path = expand_proc_sub(&inner, false)?;
result.push_str(&fd_path);
}
PROC_SUB_IN =>{
let mut inner = String::new();
while let Some(ch) = chars.next() {
match ch {
PROC_SUB_IN => break,
_ => inner.push(ch)
}
}
let fd_path = expand_proc_sub(&inner, true)?;
result.push_str(&fd_path);
}
VAR_SUB => {
flog!(INFO, chars);
let expanded = expand_var(chars)?;
@@ -358,6 +384,41 @@ pub fn expand_arithmetic(raw: &str) -> ShResult<String> {
Ok(result.to_string())
}
pub fn expand_proc_sub(raw: &str, is_input: bool) -> ShResult<String> {
// FIXME: Still a lot of issues here
// Seems like debugging will be a massive effort
let (rpipe, wpipe) = IoMode::get_pipes();
let rpipe_raw = rpipe.src_fd();
let wpipe_raw = wpipe.src_fd();
let (proc_fd, register_fd, redir_type, path) = match is_input {
false => (wpipe, rpipe, RedirType::Output, format!("/proc/self/fd/{}", rpipe_raw)),
true => (rpipe, wpipe, RedirType::Input, format!("/proc/self/fd/{}", wpipe_raw)),
};
match unsafe { fork()? } {
ForkResult::Child => {
let redir = Redir::new(proc_fd, redir_type);
let io_frame = IoFrame::from_redir(redir);
let mut io_stack = IoStack::new();
io_stack.push_frame(io_frame);
if let Err(e) = exec_input(raw.to_string(), Some(io_stack)) {
eprintln!("{e}");
exit(1);
}
exit(0);
}
ForkResult::Parent { child } => {
write_jobs(|j| j.register_fd(child, register_fd));
let registered = read_jobs(|j| j.registered_fds().to_vec());
flog!(DEBUG,registered);
// Do not wait; process may run in background
Ok(path)
}
}
}
/// Get the command output of a given command input as a String
pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
flog!(DEBUG, "in expand_cmd_sub");
@@ -369,17 +430,14 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
}
let (rpipe,wpipe) = IoMode::get_pipes();
let cmd_sub_redir = Redir::new(wpipe, RedirType::Output);
let mut cmd_sub_io_frame = IoFrame::from_redir(cmd_sub_redir);
let cmd_sub_io_frame = IoFrame::from_redir(cmd_sub_redir);
let mut io_stack = IoStack::new();
let mut io_buf = IoBuf::new(rpipe);
match unsafe { fork()? } {
ForkResult::Child => {
if let Err(e) = cmd_sub_io_frame.redirect() {
eprintln!("{e}");
exit(1);
}
if let Err(e) = exec_input(raw.to_string()) {
io_stack.push_frame(cmd_sub_io_frame);
if let Err(e) = exec_input(raw.to_string(), Some(io_stack)) {
eprintln!("{e}");
exit(1);
}
@@ -411,7 +469,6 @@ pub fn unescape_str(raw: &str) -> String {
while let Some(ch) = chars.next() {
flog!(DEBUG,result);
match ch {
'~' if first_char => {
result.push(TILDE_SUB)
@@ -464,10 +521,9 @@ pub fn unescape_str(raw: &str) -> String {
result.push(VAR_SUB);
if chars.peek() == Some(&'(') {
chars.next();
let mut cmdsub_count = 1;
let mut paren_count = 1;
result.push(SUBSH);
while let Some(subsh_ch) = chars.next() {
flog!(DEBUG, subsh_ch);
match subsh_ch {
'\\' => {
result.push(subsh_ch);
@@ -475,13 +531,13 @@ pub fn unescape_str(raw: &str) -> String {
result.push(next_ch)
}
}
'$' if chars.peek() == Some(&'(') => {
'(' => {
result.push(subsh_ch);
cmdsub_count += 1;
paren_count += 1;
}
')' => {
cmdsub_count -= 1;
if cmdsub_count <= 0 {
paren_count -= 1;
if paren_count <= 0 {
result.push(SUBSH);
break
} else {
@@ -513,12 +569,69 @@ pub fn unescape_str(raw: &str) -> String {
}
}
}
'<' if chars.peek() == Some(&'(') => {
chars.next();
let mut paren_count = 1;
result.push(PROC_SUB_OUT);
while let Some(subsh_ch) = chars.next() {
match subsh_ch {
'\\' => {
result.push(subsh_ch);
if let Some(next_ch) = chars.next() {
result.push(next_ch)
}
}
'(' => {
result.push(subsh_ch);
paren_count += 1;
}
')' => {
paren_count -= 1;
if paren_count <= 0 {
result.push(PROC_SUB_OUT);
break
} else {
result.push(subsh_ch);
}
}
_ => result.push(subsh_ch),
}
}
}
'>' if chars.peek() == Some(&'(') => {
chars.next();
let mut paren_count = 1;
result.push(PROC_SUB_IN);
while let Some(subsh_ch) = chars.next() {
match subsh_ch {
'\\' => {
result.push(subsh_ch);
if let Some(next_ch) = chars.next() {
result.push(next_ch)
}
}
'(' => {
result.push(subsh_ch);
paren_count += 1;
}
')' => {
paren_count -= 1;
if paren_count <= 0 {
result.push(PROC_SUB_IN);
break
} else {
result.push(subsh_ch);
}
}
_ => result.push(subsh_ch),
}
}
}
'$' => result.push(VAR_SUB),
_ => result.push(ch)
}
first_char = false;
}
flog!(DEBUG, result);
result
}
@@ -1277,16 +1390,16 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
}
}
PromptTk::Pwd => {
let mut pwd = std::env::var("PWD")?;
let home = std::env::var("HOME")?;
let mut pwd = std::env::var("PWD").unwrap();
let home = std::env::var("HOME").unwrap();
if pwd.starts_with(&home) {
pwd = pwd.replacen(&home, "~", 1);
}
result.push_str(&pwd);
}
PromptTk::PwdShort => {
let mut path = std::env::var("PWD")?;
let home = std::env::var("HOME")?;
let mut path = std::env::var("PWD").unwrap();
let home = std::env::var("HOME").unwrap();
if path.starts_with(&home) {
path = path.replacen(&home, "~", 1);
}
@@ -1305,17 +1418,17 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
result.push_str(&path_rebuilt);
}
PromptTk::Hostname => {
let hostname = std::env::var("HOSTNAME")?;
let hostname = std::env::var("HOST").unwrap();
result.push_str(&hostname);
}
PromptTk::HostnameShort => todo!(),
PromptTk::ShellName => result.push_str("fern"),
PromptTk::Username => {
let username = std::env::var("USER")?;
let username = std::env::var("USER").unwrap();
result.push_str(&username);
}
PromptTk::PromptSymbol => {
let uid = std::env::var("UID")?;
let uid = std::env::var("UID").unwrap();
let symbol = if &uid == "0" {
'#'
} else {

View File

@@ -24,7 +24,7 @@ use crate::signal::sig_setup;
use crate::state::source_rc;
use crate::prelude::*;
use clap::Parser;
use state::write_vars;
use state::{read_vars, write_vars};
#[derive(Parser,Debug)]
struct FernArgs {
@@ -37,8 +37,20 @@ struct FernArgs {
version: bool
}
/// Force evaluation of lazily-initialized values early in shell startup.
///
/// In particular, this ensures that the variable table is initialized, which populates
/// environment variables from the system. If this initialization is deferred too long,
/// features like prompt expansion may fail due to missing environment variables.
///
/// This function triggers initialization by calling `read_vars` with a no-op closure,
/// which forces access to the variable table and causes its `LazyLock` constructor to run.
fn kickstart_lazy_evals() {
read_vars(|_| {});
}
fn main() {
kickstart_lazy_evals();
let args = FernArgs::parse();
if args.version {
println!("fern {}", env!("CARGO_PKG_VERSION"));
@@ -68,7 +80,7 @@ fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) {
write_vars(|v| v.bpush_arg(arg))
}
if let Err(e) = exec_input(input) {
if let Err(e) = exec_input(input,None) {
eprintln!("{e}");
exit(1);
}
@@ -94,7 +106,7 @@ fn fern_interactive() {
Err(e) => {
eprintln!("{e}");
readline_err_count += 1;
if readline_err_count == 5 {
if readline_err_count == 20 {
eprintln!("reached maximum readline error count, exiting");
break
} else {
@@ -103,7 +115,7 @@ fn fern_interactive() {
}
};
if let Err(e) = exec_input(input) {
if let Err(e) = exec_input(input,None) {
eprintln!("{e}");
}
}

View File

@@ -1,4 +1,4 @@
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, procio::borrow_fd, state::{self, set_status, write_jobs}};
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, procio::{borrow_fd, IoMode}, state::{self, set_status, write_jobs}};
pub const SIG_EXIT_OFFSET: i32 = 128;
@@ -120,12 +120,19 @@ impl ChildProc {
}
}
#[derive(Clone,Debug)]
pub struct RegisteredFd {
pub fd: IoMode,
pub owner_pid: Pid,
}
#[derive(Default,Debug)]
pub struct JobTab {
fg: Option<Job>,
order: Vec<usize>,
new_updates: Vec<usize>,
jobs: Vec<Option<Job>>
jobs: Vec<Option<Job>>,
fd_registry: Vec<RegisteredFd>
}
impl JobTab {
@@ -154,6 +161,19 @@ impl JobTab {
pub fn prev_job(&self) -> Option<usize> {
self.order.last().copied()
}
pub fn close_job_fds(&mut self, pid: Pid) {
self.fd_registry.retain(|fd| fd.owner_pid != pid)
}
pub fn registered_fds(&self) -> &[RegisteredFd] {
&self.fd_registry
}
pub fn register_fd(&mut self, owner_pid: Pid, fd: IoMode) {
let registered_fd = RegisteredFd {
fd,
owner_pid
};
self.fd_registry.push(registered_fd)
}
fn prune_jobs(&mut self) {
while let Some(job) = self.jobs.last() {
if job.is_none() {

View File

@@ -39,7 +39,7 @@ impl ExecArgs {
}
}
pub fn exec_input(input: String) -> ShResult<()> {
pub fn exec_input(input: String, io_stack: Option<IoStack>) -> ShResult<()> {
write_meta(|m| m.start_timer());
let log_tab = LOGIC_TABLE.read().unwrap();
let input = expand_aliases(input, HashSet::new(), &log_tab);
@@ -53,6 +53,9 @@ pub fn exec_input(input: String) -> ShResult<()> {
}
let mut dispatcher = Dispatcher::new(parser.extract_nodes());
if let Some(mut stack) = io_stack {
dispatcher.io_stack.extend(stack.drain(..));
}
dispatcher.begin_dispatch()
}
@@ -176,7 +179,7 @@ impl Dispatcher {
let subsh_body = subsh.0.to_string();
let snapshot = get_snapshots();
if let Err(e) = exec_input(subsh_body) {
if let Err(e) = exec_input(subsh_body, None) {
restore_snapshot(snapshot);
return Err(e)
}

View File

@@ -2,7 +2,7 @@ use std::{collections::VecDeque, fmt::Display, iter::Peekable, ops::{Bound, Dere
use bitflags::bitflags;
use crate::{builtin::BUILTINS, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::CharDequeUtils}, parse::parse_err_full, prelude::*};
use crate::{builtin::BUILTINS, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::CharDequeUtils}, prelude::*};
pub const KEYWORDS: [&str;16] = [
"if",
@@ -136,15 +136,16 @@ impl Display for Tk {
bitflags! {
#[derive(Debug,Clone,Copy,PartialEq,Default)]
pub struct TkFlags: u32 {
const KEYWORD = 0b0000000000000001;
/// This is a keyword that opens a new block statement, like 'if' and 'while'
const OPENER = 0b0000000000000010;
const IS_CMD = 0b0000000000000100;
const IS_SUBSH = 0b0000000000001000;
const IS_CMDSUB = 0b0000000000010000;
const IS_OP = 0b0000000000100000;
const ASSIGN = 0b0000000001000000;
const BUILTIN = 0b0000000010000000;
const KEYWORD = 0b0000000000000001;
/// This is a keyword that opens a new block statement, like 'if' and 'while'
const OPENER = 0b0000000000000010;
const IS_CMD = 0b0000000000000100;
const IS_SUBSH = 0b0000000000001000;
const IS_CMDSUB = 0b0000000000010000;
const IS_OP = 0b0000000000100000;
const ASSIGN = 0b0000000001000000;
const BUILTIN = 0b0000000010000000;
const IS_PROCSUB = 0b0000000100000000;
}
}
@@ -242,6 +243,9 @@ impl LexStream {
while let Some(ch) = chars.next() {
match ch {
'>' => {
if chars.peek() == Some(&'(') {
return None // It's a process sub
}
pos += 1;
if let Some('>') = chars.peek() {
chars.next();
@@ -277,6 +281,9 @@ impl LexStream {
}
}
'<' => {
if chars.peek() == Some(&'(') {
return None // It's a process sub
}
pos += 1;
for _ in 0..2 {
@@ -327,7 +334,6 @@ impl LexStream {
}
while let Some(ch) = chars.next() {
flog!(DEBUG, "we are in the loop");
match ch {
_ if self.flags.contains(LexFlags::RAW) => {
if ch.is_whitespace() {
@@ -342,61 +348,6 @@ impl LexStream {
pos += ch.len_utf8();
}
}
'`' => {
let arith_pos = pos;
pos += 1;
let mut closed = false;
while let Some(arith_ch) = chars.next() {
match arith_ch {
'\\' => {
pos += 1;
if let Some(ch) = chars.next() {
pos += ch.len_utf8();
}
}
'`' => {
pos += 1;
closed = true;
break
}
'$' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
let mut cmdsub_count = 1;
while let Some(cmdsub_ch) = chars.next() {
match cmdsub_ch {
'$' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
cmdsub_count += 1;
}
')' => {
pos += 1;
cmdsub_count -= 1;
if cmdsub_count == 0 {
break
}
}
_ => pos += cmdsub_ch.len_utf8()
}
}
}
_ => pos += arith_ch.len_utf8()
}
}
flog!(DEBUG, "we have left the loop");
flog!(DEBUG, closed);
if !closed && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
self.cursor = pos;
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Unclosed arithmetic substitution",
Span::new(arith_pos..arith_pos + 1, self.source.clone())
)
)
}
}
'$' if chars.peek() == Some(&'{') => {
pos += 2;
chars.next();
@@ -424,6 +375,90 @@ impl LexStream {
}
}
}
'<' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
let mut paren_count = 1;
let paren_pos = pos;
while let Some(ch) = chars.next() {
match ch {
'\\' => {
pos += 1;
if let Some(next_ch) = chars.next() {
pos += next_ch.len_utf8();
}
}
'(' => {
pos += 1;
paren_count += 1;
}
')' => {
pos += 1;
paren_count -= 1;
if paren_count <= 0 {
break
}
}
_ => pos += ch.len_utf8()
}
}
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
self.cursor = pos;
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Unclosed subshell",
Span::new(paren_pos..paren_pos + 1, self.source.clone())
)
)
}
let mut proc_sub_tk = self.get_token(self.cursor..pos, TkRule::Str);
proc_sub_tk.flags |= TkFlags::IS_PROCSUB;
self.cursor = pos;
return Ok(proc_sub_tk)
}
'>' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
let mut paren_count = 1;
let paren_pos = pos;
while let Some(ch) = chars.next() {
match ch {
'\\' => {
pos += 1;
if let Some(next_ch) = chars.next() {
pos += next_ch.len_utf8();
}
}
'(' => {
pos += 1;
paren_count += 1;
}
')' => {
pos += 1;
paren_count -= 1;
if paren_count <= 0 {
break
}
}
_ => pos += ch.len_utf8()
}
}
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
self.cursor = pos;
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Unclosed subshell",
Span::new(paren_pos..paren_pos + 1, self.source.clone())
)
)
}
let mut proc_sub_tk = self.get_token(self.cursor..pos, TkRule::Str);
proc_sub_tk.flags |= TkFlags::IS_PROCSUB;
self.cursor = pos;
return Ok(proc_sub_tk)
}
'$' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
@@ -507,7 +542,6 @@ impl LexStream {
subsh_tk.flags |= TkFlags::IS_SUBSH;
self.cursor = pos;
self.set_next_is_cmd(true);
flog!(DEBUG, "returning subsh tk");
return Ok(subsh_tk)
}
'{' if pos == self.cursor && self.next_is_cmd() => {
@@ -672,8 +706,6 @@ impl LexStream {
impl Iterator for LexStream {
type Item = ShResult<Tk>;
fn next(&mut self) -> Option<Self::Item> {
flog!(DEBUG,self.cursor);
flog!(DEBUG,self.source.len());
assert!(self.cursor <= self.source.len());
// We are at the end of the input
if self.cursor == self.source.len() {

View File

@@ -529,7 +529,6 @@ impl ParseStream {
/// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom
/// The check_pipelines parameter is used to prevent left-recursion issues in self.parse_pipeln()
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node>> {
flog!(DEBUG, self.tokens);
try_match!(self.parse_func_def()?);
try_match!(self.parse_brc_grp(false /* from_func_def */)?);
try_match!(self.parse_case()?);
@@ -618,15 +617,12 @@ impl ParseStream {
fn parse_test(&mut self) -> ShResult<Option<Node>> {
let mut node_tks: Vec<Tk> = vec![];
let mut cases: Vec<TestCase> = vec![];
flog!(INFO, self.check_keyword("[["));
if !self.check_keyword("[[") || !self.next_tk_is_some() {
return Ok(None)
}
node_tks.push(self.next_tk().unwrap());
let mut case_builder = TestCaseBuilder::new();
while let Some(tk) = self.next_tk() {
flog!(DEBUG, case_builder);
flog!(DEBUG, tk.as_str());
node_tks.push(tk.clone());
if tk.as_str() == "]]" {
if case_builder.can_build() {
@@ -642,24 +638,19 @@ impl ParseStream {
}
}
if case_builder.is_empty() {
flog!(DEBUG, "case builder is empty");
match tk.as_str() {
_ if TEST_UNARY_OPS.contains(&tk.as_str()) => case_builder = case_builder.with_operator(tk.clone()),
_ => case_builder = case_builder.with_lhs(tk.clone())
}
continue
} else if case_builder.operator.is_some() && case_builder.rhs.is_none() {
flog!(DEBUG, "op is some, rhs is none");
case_builder = case_builder.with_rhs(tk.clone());
continue
} else if case_builder.lhs.is_some() && case_builder.operator.is_none() {
flog!(DEBUG, "lhs is some, op is none");
// we got lhs, then rhs → treat it as operator maybe?
case_builder = case_builder.with_operator(tk.clone());
continue
} else if let TkRule::And | TkRule::Or = tk.class {
flog!(DEBUG, "found conjunction");
flog!(DEBUG, tk.class);
if case_builder.can_build() {
if case_builder.conjunct.is_some() {
return Err(
@@ -674,7 +665,6 @@ impl ParseStream {
case_builder = case_builder.with_conjunction(op);
let case = case_builder.build_and_take();
cases.push(case);
flog!(DEBUG, case_builder);
continue
} else {
return Err(
@@ -694,7 +684,6 @@ impl ParseStream {
redirs: vec![],
tokens: node_tks
};
flog!(DEBUG, node);
Ok(Some(node))
}
fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult<Option<Node>> {
@@ -1114,7 +1103,6 @@ impl ParseStream {
node_tks.push(loop_tk);
self.catch_separator(&mut node_tks);
flog!(DEBUG, node_tks);
let Some(cond) = self.parse_block(true)? else {
self.panic_mode(&mut node_tks);
return Err(parse_err_full(
@@ -1225,7 +1213,6 @@ impl ParseStream {
return Ok(None)
}
}
flog!(DEBUG, argv);
if argv.is_empty() && assignments.is_empty() {
return Ok(None)

View File

@@ -6,7 +6,7 @@ use std::path::Path;
use readline::FernReadline;
use rustyline::{error::ReadlineError, history::FileHistory, ColorMode, Config, Editor};
use crate::{expand::expand_prompt, libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, state::read_shopts};
use crate::{expand::expand_prompt, libsh::error::ShResult, prelude::*, state::read_shopts};
/// Initialize the line editor
fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> {
@@ -45,7 +45,11 @@ fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> {
fn get_prompt() -> ShResult<String> {
let Ok(prompt) = env::var("PS1") else {
return Ok("$ ".styled(Style::Green | Style::Bold))
// username@hostname
// short/path/to/pwd/
// $
let default = "\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$ ";
return Ok(format!("\n{}",expand_prompt(default)?))
};
Ok(format!("\n{}",expand_prompt(&prompt)?))

View File

@@ -120,6 +120,7 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
* We can reasonably assume that if it is not a foreground job, then it exists in the job table
* If this assumption is incorrect, the code has gone wrong somewhere.
*/
write_jobs(|j| j.close_job_fds(pid));
if let Some((
pgid,
is_fg,
@@ -132,6 +133,7 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
job.update_by_id(JobID::Pid(pid), status).unwrap();
let is_finished = !job.running();
if let Some(child) = job.children_mut().iter_mut().find(|chld| pid == chld.pid()) {
child.set_stat(status);
}

View File

@@ -170,7 +170,7 @@ impl VarTab {
let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default();
env::set_var("IFS", " \t\n");
env::set_var("HOSTNAME", hostname);
env::set_var("HOST", hostname.clone());
env::set_var("UID", uid.to_string());
env::set_var("PPID", getppid().to_string());
env::set_var("TMPDIR", "/tmp");
@@ -448,6 +448,6 @@ pub fn source_file(path: PathBuf) -> ShResult<()> {
let mut buf = String::new();
file.read_to_string(&mut buf)?;
exec_input(buf)?;
exec_input(buf,None)?;
Ok(())
}