@@ -22,6 +22,7 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
2222use std:: borrow:: Cow ;
2323use std:: ffi:: { OsStr , OsString } ;
2424use std:: fs:: { FileType , Metadata } ;
25+ use std:: io:: Write ;
2526use std:: os:: unix:: fs:: { FileTypeExt , MetadataExt } ;
2627use std:: os:: unix:: prelude:: OsStrExt ;
2728use std:: path:: Path ;
@@ -119,6 +120,7 @@ impl std::str::FromStr for QuotingStyle {
119120#[ derive( Debug , PartialEq , Eq ) ]
120121enum Token {
121122 Char ( char ) ,
123+ Byte ( u8 ) ,
122124 Directive {
123125 flag : Flags ,
124126 width : usize ,
@@ -362,6 +364,7 @@ fn get_quoted_file_name(
362364
363365fn process_token_fs ( t : & Token , meta : StatFs , display_name : & str ) {
364366 match * t {
367+ Token :: Byte ( byte) => write_raw_byte ( byte) ,
365368 Token :: Char ( c) => print ! ( "{c}" ) ,
366369 Token :: Directive {
367370 flag,
@@ -512,6 +515,10 @@ fn print_unsigned_hex(
512515 pad_and_print ( & s, flags. left , width, padding_char) ;
513516}
514517
518+ fn write_raw_byte ( byte : u8 ) {
519+ std:: io:: stdout ( ) . write_all ( & [ byte] ) . unwrap ( ) ;
520+ }
521+
515522impl Stater {
516523 fn process_flags ( chars : & [ char ] , i : & mut usize , bound : usize , flag : & mut Flags ) {
517524 while * i < bound {
@@ -614,33 +621,49 @@ impl Stater {
614621 return Token :: Char ( '\\' ) ;
615622 }
616623 match chars[ * i] {
617- 'x' if * i + 1 < bound => {
618- if let Some ( ( c, offset) ) = format_str[ * i + 1 ..] . scan_char ( 16 ) {
619- * i += offset;
620- Token :: Char ( c)
621- } else {
622- show_warning ! ( "unrecognized escape '\\ x'" ) ;
623- Token :: Char ( 'x' )
624+ 'a' => Token :: Byte ( 0x07 ) , // BEL
625+ 'b' => Token :: Byte ( 0x08 ) , // Backspace
626+ 'f' => Token :: Byte ( 0x0C ) , // Form feed
627+ 'n' => Token :: Byte ( 0x0A ) , // Line feed
628+ 'r' => Token :: Byte ( 0x0D ) , // Carriage return
629+ 't' => Token :: Byte ( 0x09 ) , // Horizontal tab
630+ '\\' => Token :: Byte ( b'\\' ) , // Backslash
631+ '\'' => Token :: Byte ( b'\'' ) , // Single quote
632+ '"' => Token :: Byte ( b'"' ) , // Double quote
633+ '0' ..='7' => {
634+ // Parse octal escape sequence (up to 3 digits)
635+ let mut value = 0u8 ;
636+ let mut count = 0 ;
637+ while * i < bound && count < 3 {
638+ if let Some ( digit) = chars[ * i] . to_digit ( 8 ) {
639+ value = value * 8 + digit as u8 ;
640+ * i += 1 ;
641+ count += 1 ;
642+ } else {
643+ break ;
644+ }
624645 }
646+ * i -= 1 ; // Adjust index to account for the outer loop increment
647+ Token :: Byte ( value)
625648 }
626- '0' ..='7' => {
627- let ( c, offset) = format_str[ * i..] . scan_char ( 8 ) . unwrap ( ) ;
628- * i += offset - 1 ;
629- Token :: Char ( c)
649+ 'x' => {
650+ // Parse hexadecimal escape sequence
651+ if * i + 1 < bound {
652+ if let Some ( ( c, offset) ) = format_str[ * i + 1 ..] . scan_char ( 16 ) {
653+ * i += offset;
654+ Token :: Byte ( c as u8 )
655+ } else {
656+ show_warning ! ( "unrecognized escape '\\ x'" ) ;
657+ Token :: Byte ( b'x' )
658+ }
659+ } else {
660+ show_warning ! ( "incomplete hex escape '\\ x'" ) ;
661+ Token :: Byte ( b'x' )
662+ }
630663 }
631- '"' => Token :: Char ( '"' ) ,
632- '\\' => Token :: Char ( '\\' ) ,
633- 'a' => Token :: Char ( '\x07' ) ,
634- 'b' => Token :: Char ( '\x08' ) ,
635- 'e' => Token :: Char ( '\x1B' ) ,
636- 'f' => Token :: Char ( '\x0C' ) ,
637- 'n' => Token :: Char ( '\n' ) ,
638- 'r' => Token :: Char ( '\r' ) ,
639- 't' => Token :: Char ( '\t' ) ,
640- 'v' => Token :: Char ( '\x0B' ) ,
641- c => {
642- show_warning ! ( "unrecognized escape '\\ {}'" , c) ;
643- Token :: Char ( c)
664+ other => {
665+ show_warning ! ( "unrecognized escape '\\ {}'" , other) ;
666+ Token :: Byte ( other as u8 )
644667 }
645668 }
646669 }
@@ -773,7 +796,9 @@ impl Stater {
773796 from_user : bool ,
774797 ) -> Result < ( ) , i32 > {
775798 match * t {
799+ Token :: Byte ( byte) => write_raw_byte ( byte) ,
776800 Token :: Char ( c) => print ! ( "{c}" ) ,
801+
777802 Token :: Directive {
778803 flag,
779804 width,
0 commit comments