Repository navigation
tr: accept -u for unbuffered output, as GNU, FreeBSD and macOS tr do #15228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,7 @@ tr-help-complement = use the complement of SET1 | |||||
| tr-help-delete = delete characters in SET1, do not translate | ||||||
| tr-help-squeeze = replace each sequence of a repeated character that is listed in the last specified SET, with a single occurrence of that character | ||||||
| tr-help-truncate-set1 = first truncate SET1 to length of SET2 | ||||||
| tr-help-unbuffered = guarantee that any output is unbuffered | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
We should be same with GNU, but we don't need to be same with BSD.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be changed in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 9853afb, and updated the French string to match. |
||||||
|
|
||||||
| # Error messages | ||||||
| tr-error-missing-operand = missing operand | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,8 @@ use operation::{ | |
| }; | ||
| use simd::process_input; | ||
| use std::ffi::OsString; | ||
| #[cfg(not(any(unix, target_os = "wasi")))] | ||
| use std::io::Write; | ||
| use std::io::{stdin, stdout}; | ||
| use uucore::display::Quotable; | ||
| use uucore::error::{UResult, USimpleError, UUsageError}; | ||
|
|
@@ -27,6 +29,7 @@ mod options { | |
| pub const DELETE: &str = "delete"; | ||
| pub const SQUEEZE: &str = "squeeze-repeats"; | ||
| pub const TRUNCATE_SET1: &str = "truncate-set1"; | ||
| pub const UNBUFFERED: &str = "unbuffered"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should drop long option since we need BSD compatibility only.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There isn't one,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry. |
||
| pub const SETS: &str = "sets"; | ||
| } | ||
|
|
||
|
|
@@ -99,10 +102,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { | |
| let mut locked_stdin = stdin.lock(); | ||
| // Write straight to the file descriptor: `Stdout` is line buffered, which | ||
| // costs a search for the last newline and an extra write per chunk. | ||
| // Every chunk is written as soon as it is translated, so `-u` has nothing | ||
| // left to do here. | ||
| #[cfg(any(unix, target_os = "wasi"))] | ||
| let mut output = uucore::io::RawWriter(stdout()); | ||
| #[cfg(not(any(unix, target_os = "wasi")))] | ||
| let mut output = stdout().lock(); | ||
| let mut output = FlushEachChunk { | ||
| inner: stdout().lock(), | ||
| enabled: matches.get_flag(options::UNBUFFERED), | ||
| }; | ||
|
|
||
| // According to the man page: translating only happens if deleting or if a second set is given | ||
| let translating = !delete_flag && sets.len() > 1; | ||
|
|
@@ -161,6 +169,33 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { | |
| Ok(()) | ||
| } | ||
|
|
||
| /// `Stdout` holds back what follows the last newline; with `-u`, flush it | ||
| /// after every chunk so that a partial line is not held back either. | ||
| #[cfg(not(any(unix, target_os = "wasi")))] | ||
| struct FlushEachChunk<W: Write> { | ||
| inner: W, | ||
| enabled: bool, | ||
| } | ||
|
|
||
| #[cfg(not(any(unix, target_os = "wasi")))] | ||
| impl<W: Write> Write for FlushEachChunk<W> { | ||
| fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { | ||
| self.inner.write(buf) | ||
| } | ||
|
|
||
| fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { | ||
| self.inner.write_all(buf)?; | ||
| if self.enabled { | ||
| self.inner.flush()?; | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn flush(&mut self) -> std::io::Result<()> { | ||
| self.inner.flush() | ||
| } | ||
| } | ||
|
|
||
| pub fn uu_app() -> Command { | ||
| Command::new("tr") | ||
| .version(uucore::crate_version!()) | ||
|
|
@@ -203,6 +238,12 @@ pub fn uu_app() -> Command { | |
| .action(ArgAction::SetTrue) | ||
| .overrides_with(options::TRUNCATE_SET1), | ||
| ) | ||
| .arg( | ||
| Arg::new(options::UNBUFFERED) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mask this from manpage by
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense, done in 742af96.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can drop it from |
||
| .short('u') | ||
| .help(translate!("tr-help-unbuffered")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this needed if it is hidden?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not for
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. |
||
| .action(ArgAction::SetTrue), | ||
| ) | ||
| .arg( | ||
| Arg::new(options::SETS) | ||
| .num_args(1..) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If unbuffered I/O is actually default of GNU
tr, we can just say:Noop. Existing for FreeBSD and macOS compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I missed comment about Windows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the record, GNU
trisn't unbuffered by default either, it writes through stdio. With GNUtr9.12,{ printf abc; sleep 2; printf 'def\n'; } | tr a-z A-Z | catonly printsABCafter the 2 seconds, whilestdbuf -o0 trprints it right away. On Unix it's only ourtrthat writes every chunk immediately, since #14897.This comment was marked as low quality.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry. Your doc is correct.