Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions crates/bashkit/src/builtins/awk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ enum AwkOutputTarget {
#[derive(Debug, Clone)]
enum AwkAction {
Print(Vec<AwkExpr>, Option<AwkOutputTarget>),
Printf(String, Vec<AwkExpr>, Option<AwkOutputTarget>),
Printf(AwkExpr, Vec<AwkExpr>, Option<AwkOutputTarget>),
Assign(String, AwkExpr),
ArrayAssign(String, AwkExpr, AwkExpr), // arr[key] = val
If(AwkExpr, Vec<AwkAction>, Vec<AwkAction>),
Expand Down Expand Up @@ -787,15 +787,22 @@ impl<'a> AwkParser<'a> {
self.skip_whitespace();
}

// Parse format string
if self.pos >= self.input.len() || self.current_char().unwrap() != '"' {
// Parse format string — accepts string literals or expressions
if self.pos >= self.input.len() {
self.in_print_context = false;
return Err(Error::Execution(
"awk: printf requires format string".to_string(),
));
}

let format = self.parse_string()?;
let format_expr = if self.current_char().unwrap() == '"' {
// String literal format — parse directly
let s = self.parse_string()?;
AwkExpr::String(s)
} else {
// Expression as format string (e.g., printf substr($1,1,1))
self.parse_expression()?
};
let mut args = Vec::new();

self.skip_whitespace();
Expand All @@ -814,7 +821,7 @@ impl<'a> AwkParser<'a> {
let target = self.parse_output_target()?;
self.in_print_context = false;

Ok(AwkAction::Printf(format, args, target))
Ok(AwkAction::Printf(format_expr, args, target))
}

/// Parse optional output target after print/printf arguments: `> file`, `>> file`, `| cmd`.
Expand Down Expand Up @@ -2900,9 +2907,10 @@ impl AwkInterpreter {
self.write_output(&text, target);
AwkFlow::Continue
}
AwkAction::Printf(format, args, target) => {
AwkAction::Printf(format_expr, args, target) => {
let format_str = self.eval_expr(format_expr).as_string();
let values: Vec<AwkValue> = args.iter().map(|a| self.eval_expr(a)).collect();
let text = self.format_string(format, &values);
let text = self.format_string(&format_str, &values);
self.write_output(&text, target);
AwkFlow::Continue
}
Expand Down
25 changes: 25 additions & 0 deletions crates/bashkit/tests/awk_printf_expr_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! Test for awk printf accepting expressions as format string

use bashkit::Bash;

/// Issue #810: awk printf should accept expressions (not just string literals)
#[tokio::test]
async fn awk_printf_expression_format() {
let mut bash = Bash::new();
let result = bash
.exec(r#"echo "my-project" | awk '{for(i=1;i<=NF;i++) printf substr($i,1,1)}'"#)
.await
.unwrap();
assert_eq!(result.stdout.trim(), "m");
}

/// Printf with string literal should still work
#[tokio::test]
async fn awk_printf_string_literal() {
let mut bash = Bash::new();
let result = bash
.exec(r#"echo test | awk '{printf "%s\n", $1}'"#)
.await
.unwrap();
assert_eq!(result.stdout.trim(), "test");
}
Loading