implemented PIPESTATUS variable from bash. puts all exit codes from last pipeline into an array.

This commit is contained in:
2026-03-15 23:32:57 -04:00
parent 067b4f6184
commit bcc4a87e10
2 changed files with 49 additions and 9 deletions

View File

@@ -1,3 +1,5 @@
use std::collections::VecDeque;
use ariadne::Fmt; use ariadne::Fmt;
use scopeguard::defer; use scopeguard::defer;
use yansi::Color; use yansi::Color;
@@ -10,7 +12,7 @@ use crate::{
prelude::*, prelude::*,
procio::{IoMode, borrow_fd}, procio::{IoMode, borrow_fd},
signal::{disable_reaping, enable_reaping}, signal::{disable_reaping, enable_reaping},
state::{self, ShellParam, set_status, write_jobs, write_vars}, state::{self, ShellParam, Var, VarFlags, VarKind, set_status, write_jobs, write_vars},
}; };
pub const SIG_EXIT_OFFSET: i32 = 128; pub const SIG_EXIT_OFFSET: i32 = 128;
@@ -596,6 +598,26 @@ impl Job {
.map(|chld| chld.stat()) .map(|chld| chld.stat())
.collect::<Vec<WtStat>>() .collect::<Vec<WtStat>>()
} }
pub fn pipe_status(stats: &[WtStat]) -> Option<Vec<i32>> {
if stats.iter()
.any(|stat| matches!(stat, WtStat::StillAlive | WtStat::Continued(_) | WtStat::PtraceSyscall(_)))
|| stats.len() <= 1 {
return None;
}
Some(stats.iter()
.map(|stat| {
match stat {
WtStat::Exited(_, code) => *code,
WtStat::Signaled(_, signal, _) => SIG_EXIT_OFFSET + *signal as i32,
WtStat::Stopped(_, signal) => SIG_EXIT_OFFSET + *signal as i32,
WtStat::PtraceEvent(_, signal, _) => SIG_EXIT_OFFSET + *signal as i32,
WtStat::PtraceSyscall(_) |
WtStat::Continued(_) |
WtStat::StillAlive => unreachable!()
}
})
.collect())
}
pub fn get_pids(&self) -> Vec<Pid> { pub fn get_pids(&self) -> Vec<Pid> {
self self
.children .children
@@ -839,22 +861,30 @@ pub fn wait_fg(job: Job, interactive: bool) -> ShResult<()> {
enable_reaping(); enable_reaping();
} }
let statuses = write_jobs(|j| j.new_fg(job))?; let statuses = write_jobs(|j| j.new_fg(job))?;
for status in statuses { for status in &statuses {
code = code_from_status(&status).unwrap_or(0); code = code_from_status(status).unwrap_or(0);
match status { match status {
WtStat::Stopped(_, _) => { WtStat::Stopped(_, _) => {
was_stopped = true; was_stopped = true;
write_jobs(|j| j.fg_to_bg(status))?; write_jobs(|j| j.fg_to_bg(*status))?;
} }
WtStat::Signaled(_, sig, _) => { WtStat::Signaled(_, sig, _) => {
if sig == Signal::SIGTSTP { if *sig == Signal::SIGTSTP {
was_stopped = true; was_stopped = true;
write_jobs(|j| j.fg_to_bg(status))?; write_jobs(|j| j.fg_to_bg(*status))?;
} }
} }
_ => { /* Do nothing */ } _ => { /* Do nothing */ }
} }
} }
if let Some(pipe_status) = Job::pipe_status(&statuses) {
let pipe_status = pipe_status
.into_iter()
.map(|s| s.to_string())
.collect::<VecDeque<String>>();
write_vars(|v| v.set_var("PIPESTATUS", VarKind::Arr(pipe_status), VarFlags::NONE))?;
}
// If job wasn't stopped (moved to bg), clear the fg slot // If job wasn't stopped (moved to bg), clear the fg slot
if !was_stopped { if !was_stopped {
write_jobs(|j| { write_jobs(|j| {

View File

@@ -1,14 +1,14 @@
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering}; use std::{collections::VecDeque, sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering}};
use nix::sys::signal::{SaFlags, SigAction, sigaction}; use nix::sys::signal::{SaFlags, SigAction, sigaction};
use crate::{ use crate::{
builtin::trap::TrapTarget, builtin::trap::TrapTarget,
jobs::{JobCmdFlags, JobID, take_term}, jobs::{Job, JobCmdFlags, JobID, take_term},
libsh::error::{ShErr, ShErrKind, ShResult}, libsh::error::{ShErr, ShErrKind, ShResult},
parse::execute::exec_input, parse::execute::exec_input,
prelude::*, prelude::*,
state::{AutoCmd, AutoCmdKind, read_jobs, read_logic, write_jobs, write_meta}, state::{AutoCmd, AutoCmdKind, VarFlags, VarKind, read_jobs, read_logic, write_jobs, write_meta, write_vars},
}; };
static SIGNALS: AtomicU64 = AtomicU64::new(0); static SIGNALS: AtomicU64 = AtomicU64::new(0);
@@ -316,6 +316,16 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned()); let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
if let Some(job) = result { if let Some(job) = result {
let job_complete_msg = job.display(&job_order, JobCmdFlags::PIDS).to_string(); let job_complete_msg = job.display(&job_order, JobCmdFlags::PIDS).to_string();
let statuses = job.get_stats();
if let Some(pipe_status) = Job::pipe_status(&statuses) {
let pipe_status = pipe_status
.into_iter()
.map(|s| s.to_string())
.collect::<VecDeque<String>>();
write_vars(|v| v.set_var("PIPESTATUS", VarKind::Arr(pipe_status), VarFlags::NONE))?;
}
let post_job_hooks = read_logic(|l| l.get_autocmds(AutoCmdKind::OnJobFinish)); let post_job_hooks = read_logic(|l| l.get_autocmds(AutoCmdKind::OnJobFinish));
for cmd in post_job_hooks { for cmd in post_job_hooks {