Merge branch 'main' into hotfix-mktemp

This commit is contained in:
Zaú Júlio 2023-03-06 21:39:56 -03:00 committed by GitHub
commit b540b15b5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 1055 additions and 757 deletions

View file

@ -291,7 +291,13 @@ jobs:
shell: bash
run: |
RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items
- uses: DavidAnson/markdownlint-cli2-action@v9
with:
command: fix
globs: |
*.md
docs/src/*.md
src/uu/*/*.md
min_version:
name: MinRustV # Minimum supported rust version (aka, MinSRV or MSRV)
@ -408,6 +414,16 @@ jobs:
make test
env:
RUST_BACKTRACE: "1"
- name: "`make install`"
shell: bash
run: |
DESTDIR=/tmp/ make PROFILE=release install
# Check that the manpage is present
test -f /tmp/usr/local/share/man/man1/whoami.1
# Check that the completion is present
test -f /tmp/usr/local/share/zsh/site-functions/_install
env:
RUST_BACKTRACE: "1"
build_rust_stable:

6
.markdownlint.yaml Normal file
View file

@ -0,0 +1,6 @@
# Disable 'Line length'. Doesn't provide much values
MD013: false
# Disable 'Fenced code blocks should have a language specified'
# Doesn't provide much in src/ to enforce it
MD040: false

View file

@ -116,7 +116,7 @@ the community.
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
<https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
@ -124,5 +124,5 @@ enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
<https://www.contributor-covenant.org/faq>. Translations are available at
<https://www.contributor-covenant.org/translations>.

View file

@ -38,20 +38,19 @@ search the issues to make sure no one else is working on it.
## Platforms
We take pride in supporting many operating systems and architectures.
We take pride in supporting many operating systems and architectures.
**Tip:**
For Windows, Microsoft provides some images (VMWare, Hyper-V, VirtualBox and Parallels)
For Windows, Microsoft provides some images (VMWare, Hyper-V, VirtualBox and Parallels)
for development:
https://developer.microsoft.com/windows/downloads/virtual-machines/
<https://developer.microsoft.com/windows/downloads/virtual-machines/>
## Commit messages
To help the project maintainers review pull requests from contributors across
numerous utilities, the team has settled on conventions for commit messages.
From http://git-scm.com/book/ch5-2.html:
From <http://git-scm.com/book/ch5-2.html>:
```
Short (50 chars or less) summary of changes

17
Cargo.lock generated
View file

@ -267,6 +267,16 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "clap_mangen"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0f09a0ca8f0dd8ac92c546b426f466ef19828185c6d504c80c48c9c2768ed9"
dependencies = [
"clap",
"roff",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -324,6 +334,7 @@ dependencies = [
"chrono",
"clap",
"clap_complete",
"clap_mangen",
"conv",
"filetime",
"glob",
@ -1874,6 +1885,12 @@ dependencies = [
"libc",
]
[[package]]
name = "roff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
[[package]]
name = "rstest"
version = "0.16.0"

View file

@ -1,7 +1,7 @@
# coreutils (uutils)
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu
# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu mangen
[package]
name = "coreutils"
@ -271,6 +271,7 @@ byteorder = "1.3.2"
chrono = { version="^0.4.23", default-features=false, features=["std", "alloc", "clock"]}
clap = { version = "4.0", features = ["wrap_help", "cargo"] }
clap_complete = "4.0"
clap_mangen = "0.2"
compare = "0.1.0"
coz = { version = "0.1.3" }
crossterm = ">=0.19"
@ -352,6 +353,7 @@ clap = { workspace=true }
once_cell = { workspace=true }
uucore = { workspace=true }
clap_complete = { workspace=true }
clap_mangen = { workspace=true }
phf = { workspace=true }
selinux = { workspace=true, optional = true }
textwrap = { workspace=true }

View file

@ -1,21 +1,19 @@
Documentation
-------------
# Documentation
The source of the documentation is available on:
https://uutils.github.io/dev/coreutils/
<https://uutils.github.io/dev/coreutils/>
The documentation is updated everyday on this repository:
https://github.com/uutils/uutils.github.io/
<https://github.com/uutils/uutils.github.io/>
Running GNU tests
-----------------
## Running GNU tests
<!-- spell-checker:ignore gnulib -->
- Check out https://github.com/coreutils/coreutils next to your fork as gnu
- Check out https://github.com/coreutils/gnulib next to your fork as gnulib
- Check out <https://github.com/coreutils/coreutils> next to your fork as gnu
- Check out <https://github.com/coreutils/gnulib> next to your fork as gnulib
- Rename the checkout of your fork to uutils
At the end you should have uutils, gnu and gnulib checked out next to each other.
@ -23,9 +21,7 @@ At the end you should have uutils, gnu and gnulib checked out next to each other
- Run `cd uutils && ./util/build-gnu.sh && cd ..` to get everything ready (this may take a while)
- Finally, you can run tests with `bash uutils/util/run-gnu-test.sh <tests>`. Instead of `<tests>` insert the tests you want to run, e.g. `tests/misc/wc-proc.sh`.
Code Coverage Report Generation
---------------------------------
## Code Coverage Report Generation
<!-- spell-checker:ignore (flags) Ccodegen Coverflow Cpanic Zinstrument Zpanic -->
@ -35,14 +31,14 @@ Code coverage report can be generated using [grcov](https://github.com/mozilla/g
To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report
```bash
$ export CARGO_INCREMENTAL=0
$ export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
$ export RUSTDOCFLAGS="-Cpanic=abort"
$ cargo build <options...> # e.g., --features feat_os_unix
$ cargo test <options...> # e.g., --features feat_os_unix test_pathchk
$ grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
$ # open target/debug/coverage/index.html in browser
```shell
export CARGO_INCREMENTAL=0
export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
export RUSTDOCFLAGS="-Cpanic=abort"
cargo build <options...> # e.g., --features feat_os_unix
cargo test <options...> # e.g., --features feat_os_unix test_pathchk
grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
# open target/debug/coverage/index.html in browser
```
if changes are not reflected in the report then run `cargo clean` and run the above commands.
@ -52,19 +48,21 @@ if changes are not reflected in the report then run `cargo clean` and run the ab
If you are using stable version of Rust that doesn't enable code coverage instrumentation by default
then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above.
pre-commit hooks
----------------
## pre-commit hooks
A configuration for `pre-commit` is provided in the repository. It allows automatically checking every git commit you make to ensure it compiles, and passes `clippy` and `rustfmt` without warnings.
To use the provided hook:
1. [Install `pre-commit`](https://pre-commit.com/#install)
2. Run `pre-commit install` while in the repository directory
1. Run `pre-commit install` while in the repository directory
Your git commits will then automatically be checked. If a check fails, an error message will explain why, and your commit will be canceled. You can then make the suggested changes, and run `git commit ...` again.
### Using Clippy
## Using Clippy
The `msrv` key in the clippy configuration file `clippy.toml` is used to disable lints pertaining to newer features by specifying the minimum supported Rust version (MSRV). However, this key is only supported on `nightly`. To invoke clippy without errors, use `cargo +nightly clippy`. In order to also check tests and non-default crate features, use `cargo +nightly clippy --all-targets --all-features`.
## Markdown linter
We use <https://github.com/DavidAnson/markdownlint> to lint the Markdown files.

View file

@ -349,10 +349,12 @@ endif
mkdir -p $(DESTDIR)$(DATAROOTDIR)/zsh/site-functions
mkdir -p $(DESTDIR)$(DATAROOTDIR)/bash-completion/completions
mkdir -p $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d
mkdir -p $(DESTDIR)$(DATAROOTDIR)/man/man1
$(foreach prog, $(INSTALLEES), \
$(BUILDDIR)/coreutils completion $(prog) zsh > $(DESTDIR)$(DATAROOTDIR)/zsh/site-functions/_$(PROG_PREFIX)$(prog); \
$(BUILDDIR)/coreutils completion $(prog) bash > $(DESTDIR)$(DATAROOTDIR)/bash-completion/completions/$(PROG_PREFIX)$(prog); \
$(BUILDDIR)/coreutils completion $(prog) fish > $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d/$(PROG_PREFIX)$(prog).fish; \
$(BUILDDIR)/coreutils manpage $(prog) > $(DESTDIR)$(DATAROOTDIR)/man/man1/$(PROG_PREFIX)$(prog).1; \
)
uninstall:

216
README.md
View file

@ -12,7 +12,7 @@
-----------------------------------------------
<!-- markdownlint-disable commands-show-output no-duplicate-heading -->
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR RUNTEST UTILNAME -->
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR RUNTEST UTILNAME manpages -->
uutils is an attempt at writing universal (as in cross-platform) CLI
utilities in [Rust](http://www.rust-lang.org).
@ -21,11 +21,12 @@ or different behavior might be experienced.
To install it:
```
$ cargo install coreutils
$ ~/.cargo/bin/coreutils
```shell
cargo install coreutils
~/.cargo/bin/coreutils
```
<!-- markdownlint-disable-next-line MD026 -->
## Why?
uutils aims to work on as many platforms as possible, to be able to use the
@ -35,6 +36,7 @@ chosen not only because it is fast and safe, but is also excellent for
writing cross-platform code.
## Documentation
uutils has both user and developer documentation available:
- [User Manual](https://uutils.github.io/user/)
@ -46,8 +48,8 @@ Both can also be generated locally, the instructions for that can be found in th
<!-- ANCHOR: build (this mark is needed for mdbook) -->
## Requirements
* Rust (`cargo`, `rustc`)
* GNU Make (optional)
- Rust (`cargo`, `rustc`)
- GNU Make (optional)
### Rust Version
@ -64,9 +66,9 @@ or GNU Make.
For either method, we first need to fetch the repository:
```bash
$ git clone https://github.com/uutils/coreutils
$ cd coreutils
```shell
git clone https://github.com/uutils/coreutils
cd coreutils
```
### Cargo
@ -74,8 +76,8 @@ $ cd coreutils
Building uutils using Cargo is easy because the process is the same as for
every other Rust program:
```bash
$ cargo build --release
```shell
cargo build --release
```
This command builds the most portable common core set of uutils into a multicall
@ -85,20 +87,20 @@ Additional platform-specific uutils are often available. Building these
expanded sets of uutils for a platform (on that platform) is as simple as
specifying it as a feature:
```bash
$ cargo build --release --features macos
```shell
cargo build --release --features macos
# or ...
$ cargo build --release --features windows
cargo build --release --features windows
# or ...
$ cargo build --release --features unix
cargo build --release --features unix
```
If you don't want to build every utility available on your platform into the
final binary, you can also specify which ones you want to build manually.
For example:
```bash
$ cargo build --features "base32 cat echo rm" --no-default-features
```shell
cargo build --features "base32 cat echo rm" --no-default-features
```
If you don't want to build the multicall binary and would prefer to build
@ -107,8 +109,8 @@ is contained in its own package within the main repository, named
"uu_UTILNAME". To build individual utilities, use cargo to build just the
specific packages (using the `--package` [aka `-p`] option). For example:
```bash
$ cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm
```shell
cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm
```
### GNU Make
@ -117,80 +119,86 @@ Building using `make` is a simple process as well.
To simply build all available utilities:
```bash
$ make
```shell
make
```
In release mode:
```shell
make PROFILE=release
```
To build all but a few of the available utilities:
```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2'
```shell
make SKIP_UTILS='UTILITY_1 UTILITY_2'
```
To build only a few of the available utilities:
```bash
$ make UTILS='UTILITY_1 UTILITY_2'
```shell
make UTILS='UTILITY_1 UTILITY_2'
```
## Installation
### Cargo
### Install with Cargo
Likewise, installing can simply be done using:
```bash
$ cargo install --path .
```shell
cargo install --path .
```
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
This does not install files necessary for shell completion. For shell completion to work,
use `GNU Make` or see `Manually install shell completions`.
This does not install files necessary for shell completion or manpages.
For manpages or shell completion to work, use `GNU Make` or see `Manually install shell completions`/`Manually install manpages`.
### GNU Make
### Install with GNU Make
To install all available utilities:
```bash
$ make install
```shell
make install
```
To install using `sudo` switch `-E` must be used:
```bash
$ sudo -E make install
```shell
sudo -E make install
```
To install all but a few of the available utilities:
```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' install
```shell
make SKIP_UTILS='UTILITY_1 UTILITY_2' install
```
To install only a few of the available utilities:
```bash
$ make UTILS='UTILITY_1 UTILITY_2' install
```shell
make UTILS='UTILITY_1 UTILITY_2' install
```
To install every program with a prefix (e.g. uu-echo uu-cat):
```bash
$ make PROG_PREFIX=PREFIX_GOES_HERE install
```shell
make PROG_PREFIX=PREFIX_GOES_HERE install
```
To install the multicall binary:
```bash
$ make MULTICALL=y install
```shell
make MULTICALL=y install
```
Set install parent directory (default value is /usr/local):
```bash
```shell
# DESTDIR is also supported
$ make PREFIX=/my/path install
make PREFIX=/my/path install
```
Installing with `make` installs shell completions for all installed utilities
@ -203,123 +211,139 @@ The `coreutils` binary can generate completions for the `bash`, `elvish`, `fish`
and `zsh` shells. It prints the result to stdout.
The syntax is:
```bash
```shell
cargo run completion <utility> <shell>
```
So, to install completions for `ls` on `bash` to `/usr/local/share/bash-completion/completions/ls`,
run:
```bash
```shell
cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
```
### Manually install manpages
To generate manpages, the syntax is:
```bash
cargo run manpage <utility>
```
So, to install the manpage for `ls` to `/usr/local/share/man/man1/ls.1`
run:
```bash
cargo run manpage ls > /usr/local/share/man/man1/ls.1
```
## Un-installation
Un-installation differs depending on how you have installed uutils. If you used
Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use
Make to uninstall.
### Cargo
### Uninstall with Cargo
To uninstall uutils:
```bash
$ cargo uninstall uutils
```shell
cargo uninstall uutils
```
### GNU Make
### Uninstall with GNU Make
To uninstall all utilities:
```bash
$ make uninstall
```shell
make uninstall
```
To uninstall every program with a set prefix:
```bash
$ make PROG_PREFIX=PREFIX_GOES_HERE uninstall
```shell
make PROG_PREFIX=PREFIX_GOES_HERE uninstall
```
To uninstall the multicall binary:
```bash
$ make MULTICALL=y uninstall
```shell
make MULTICALL=y uninstall
```
To uninstall from a custom parent directory:
```bash
```shell
# DESTDIR is also supported
$ make PREFIX=/my/path uninstall
make PREFIX=/my/path uninstall
```
<!-- ANCHOR_END: build (this mark is needed for mdbook) -->
## Testing
Testing can be done using either Cargo or `make`.
### Cargo
### Testing with Cargo
Just like with building, we follow the standard procedure for testing using
Cargo:
```bash
$ cargo test
```shell
cargo test
```
By default, `cargo test` only runs the common programs. To run also platform
specific tests, run:
```bash
$ cargo test --features unix
```shell
cargo test --features unix
```
If you would prefer to test a select few utilities:
```bash
$ cargo test --features "chmod mv tail" --no-default-features
```shell
cargo test --features "chmod mv tail" --no-default-features
```
If you also want to test the core utilities:
```bash
$ cargo test -p uucore -p coreutils
```shell
cargo test -p uucore -p coreutils
```
To debug:
```bash
$ gdb --args target/debug/coreutils ls
```shell
gdb --args target/debug/coreutils ls
(gdb) b ls.rs:79
(gdb) run
```
### GNU Make
### Testing with GNU Make
To simply test all available utilities:
```bash
$ make test
```shell
make test
```
To test all but a few of the available utilities:
```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' test
```shell
make SKIP_UTILS='UTILITY_1 UTILITY_2' test
```
To test only a few of the available utilities:
```bash
$ make UTILS='UTILITY_1 UTILITY_2' test
```shell
make UTILS='UTILITY_1 UTILITY_2' test
```
To include tests for unimplemented behavior:
```bash
$ make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
```shell
make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
```
### Run Busybox Tests
@ -329,20 +353,20 @@ requires `make`.
To run busybox tests for all utilities for which busybox has tests
```bash
$ make busytest
```shell
make busytest
```
To run busybox tests for a few of the available utilities
```bash
$ make UTILS='UTILITY_1 UTILITY_2' busytest
```shell
make UTILS='UTILITY_1 UTILITY_2' busytest
```
To pass an argument like "-v" to the busybox test runtime
```bash
$ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
```shell
make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
```
### Comparing with GNU
@ -355,15 +379,15 @@ breakdown of the GNU test results of the main branch can be found
To run locally:
```bash
$ bash util/build-gnu.sh
$ bash util/run-gnu-test.sh
```shell
bash util/build-gnu.sh
bash util/run-gnu-test.sh
# To run a single test:
$ bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example
bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example
# To run several tests:
$ bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example
bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example
# If this is a perl (.pl) test, to run in debug:
$ DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl
DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl
```
Note that it relies on individual utilities (not the multicall binary).
@ -387,7 +411,6 @@ To improve the GNU compatibility, the following process is recommended:
1. Start to modify the Rust implementation to match the expected behavior
1. Add a test to make sure that we don't regress (our test suite is super quick)
## Contributing
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
@ -395,11 +418,12 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
## Utilities
Please note that this is not fully accurate:
* Some new options can be added / removed in the GNU implementation;
* Some error management might be missing;
* Some behaviors might be different.
See https://github.com/uutils/coreutils/issues/3336 for the main meta bugs
- Some new options can be added / removed in the GNU implementation;
- Some error management might be missing;
- Some behaviors might be different.
See <https://github.com/uutils/coreutils/issues/3336> for the main meta bugs
(many are missing).
| Done | WIP |

View file

@ -1,3 +1,3 @@
# Build from source
{{#include ../../README.md:build }}
{{#include ../../README.md:build }}

View file

@ -1 +1,3 @@
{{ #include ../../CONTRIBUTING.md }}
<!-- markdownlint-disable MD041 -->
{{ #include ../../CONTRIBUTING.md }}

View file

@ -1,5 +1,9 @@
<!-- markdownlint-disable MD041 -->
{{#include logo.svg}}
<!-- markdownlint-disable MD033 -->
<style>
/* Make the logo a bit bigger and center */
#logo {

View file

@ -11,9 +11,10 @@ You can also [build uutils from source](/build.md).
<!-- toc -->
## Cargo
[![crates.io package](https://repology.org/badge/version-for-repo/crates_io/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions)
```bash
```shell
# Linux
cargo install coreutils --features unix
# MacOs
@ -23,11 +24,12 @@ cargo install coreutils --features windows
```
## Linux
### Alpine
[![Alpine Linux Edge package](https://repology.org/badge/version-for-repo/alpine_edge/uutils-coreutils.svg)](https://pkgs.alpinelinux.org/packages?name=uutils-coreutils)
```bash
```shell
apk update uutils-coreutils
```
@ -37,7 +39,7 @@ apk update uutils-coreutils
[![Arch package](https://repology.org/badge/version-for-repo/arch/uutils-coreutils.svg)](https://archlinux.org/packages/community/x86_64/uutils-coreutils/)
```bash
```shell
pacman -S uutils-coreutils
```
@ -45,7 +47,7 @@ pacman -S uutils-coreutils
[![Debian package](https://repology.org/badge/version-for-repo/debian_unstable/uutils-coreutils.svg)](https://packages.debian.org/sid/source/rust-coreutils)
```bash
```shell
apt install rust-coreutils
# To use it:
export PATH=/usr/lib/cargo/bin/coreutils:$PATH
@ -57,32 +59,35 @@ export PATH=/usr/lib/cargo/bin/coreutils:$PATH
[![Gentoo package](https://repology.org/badge/version-for-repo/gentoo/uutils-coreutils.svg)](https://packages.gentoo.org/packages/sys-apps/uutils-coreutils)
```bash
```shell
emerge -pv sys-apps/uutils-coreutils
```
### Manjaro
![Manjaro Stable package](https://repology.org/badge/version-for-repo/manjaro_stable/uutils-coreutils.svg)
[![Manjaro Testing package](https://repology.org/badge/version-for-repo/manjaro_testing/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions)
[![Manjaro Unstable package](https://repology.org/badge/version-for-repo/manjaro_unstable/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions)
```bash
```shell
pacman -S uutils-coreutils
# or
pamac install uutils-coreutils
```
### NixOS
[![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions)
```bash
```shell
nix-env -iA nixos.uutils-coreutils
```
### OpenMandriva Lx
[![openmandriva cooker package](https://repology.org/badge/version-for-repo/openmandriva_cooker/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions)
```bash
```shell
dnf install uutils-coreutils
```
@ -90,7 +95,7 @@ dnf install uutils-coreutils
[![Ubuntu package](https://repology.org/badge/version-for-repo/ubuntu_23_04/uutils-coreutils.svg)](https://packages.ubuntu.com/source/lunar/rust-coreutils)
```bash
```shell
apt install rust-coreutils
# To use it:
export PATH=/usr/lib/cargo/bin/coreutils:$PATH
@ -101,13 +106,15 @@ export PATH=/usr/lib/cargo/bin/coreutils:$PATH
## MacOS
### Homebrew
[![Homebrew package](https://repology.org/badge/version-for-repo/homebrew/uutils-coreutils.svg)](https://formulae.brew.sh/formula/uutils-coreutils)
```bash
```shell
brew install uutils-coreutils
```
### MacPorts
[![MacPorts package](https://repology.org/badge/version-for-repo/macports/uutils-coreutils.svg)](https://ports.macports.org/port/coreutils-uutils/)
```
@ -115,18 +122,20 @@ port install coreutils-uutils
```
## FreeBSD
[![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions)
```sh
pkg install uutils
pkg install rust-coreutils
```
## Windows
### Scoop
[![Scoop package](https://repology.org/badge/version-for-repo/scoop/uutils-coreutils.svg)](https://scoop.sh/#/apps?q=uutils-coreutils&s=0&d=1&o=true)
```bash
```shell
scoop install uutils-coreutils
```
@ -136,4 +145,6 @@ scoop install uutils-coreutils
[![AUR package](https://repology.org/badge/version-for-repo/aur/coreutils-hybrid.svg)](https://aur.archlinux.org/packages/coreutils-hybrid)
A GNU coreutils / uutils coreutils hybrid package. Uses stable uutils programs mixed with GNU counterparts if uutils counterpart is unfinished or buggy.
A GNU coreutils / uutils coreutils hybrid package. Uses stable uutils
programs mixed with GNU counterparts if uutils counterpart is
unfinished or buggy.

View file

@ -1,4 +1,5 @@
# Multi-call binary
# Multi-call binary
uutils includes a multi-call binary from which the utils can be invoked. This
reduces the binary size of the binary and can be useful for portability.
@ -12,6 +13,7 @@ coreutils [util] [util options]
The `--help` flag will print a list of available utils.
## Example
```
```shell
coreutils ls -l
```
```

View file

@ -1,5 +1,7 @@
# GNU Test Coverage
<!-- markdownlint-disable MD033 -->
uutils is actively tested against the GNU coreutils test suite. The results
below are automatically updated every day.

View file

@ -5,6 +5,8 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore manpages mangen
use clap::{Arg, Command};
use clap_complete::Shell;
use std::cmp;
@ -90,6 +92,10 @@ fn main() {
gen_completions(args, &utils);
}
if util == "manpage" {
gen_manpage(args, &utils);
}
match utils.get(util) {
Some(&(uumain, _)) => {
process::exit(uumain((vec![util_os].into_iter()).chain(args)));
@ -167,6 +173,39 @@ fn gen_completions<T: uucore::Args>(
process::exit(0);
}
/// Generate the manpage for the utility in the first parameter
fn gen_manpage<T: uucore::Args>(
args: impl Iterator<Item = OsString>,
util_map: &UtilityMap<T>,
) -> ! {
let all_utilities: Vec<_> = std::iter::once("coreutils")
.chain(util_map.keys().copied())
.collect();
let matches = Command::new("manpage")
.about("Prints manpage to stdout")
.arg(
Arg::new("utility")
.value_parser(clap::builder::PossibleValuesParser::new(all_utilities))
.required(true),
)
.get_matches_from(std::iter::once(OsString::from("manpage")).chain(args));
let utility = matches.get_one::<String>("utility").unwrap();
let command = if utility == "coreutils" {
gen_coreutils_app(util_map)
} else {
util_map.get(utility).unwrap().1()
};
let man = clap_mangen::Man::new(command);
man.render(&mut io::stdout())
.expect("Man page generation failed");
io::stdout().flush().unwrap();
process::exit(0);
}
fn gen_coreutils_app<T: uucore::Args>(util_map: &UtilityMap<T>) -> Command {
let mut command = Command::new("coreutils");
for (_, (_, sub_app)) in util_map {

View file

@ -4,11 +4,8 @@
arch
```
Display machine architecture
## After Help
Determine architecture name for current machine.

View file

@ -7,8 +7,8 @@ base32 [OPTION]... [FILE]
encode/decode data and print to standard output
With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base32 alphabet in RFC
4648. When decoding, the input may contain newlines in addition
The data are encoded as described for the base32 alphabet in RFC 4648.
When decoding, the input may contain newlines in addition
to the bytes of the formal base32 alphabet. Use --ignore-garbage
to attempt to recover from any other non-alphabet bytes in the
encoded stream.

View file

@ -7,8 +7,8 @@ base64 [OPTION]... [FILE]
encode/decode data and print to standard output
With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base64 alphabet in RFC
3548. When decoding, the input may contain newlines in addition
The data are encoded as described for the base64 alphabet in RFC 3548.
When decoding, the input may contain newlines in addition
to the bytes of the formal base64 alphabet. Use --ignore-garbage
to attempt to recover from any other non-alphabet bytes in the
encoded stream.

View file

@ -7,5 +7,5 @@ chcon [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE...
chcon [OPTION]... --reference=RFILE FILE...
```
Change the SELinux security context of each FILE to CONTEXT.
With --reference, change the security context of each FILE to that of RFILE.
Change the SELinux security context of each FILE to CONTEXT.
With --reference, change the security context of each FILE to that of RFILE.

View file

@ -5,7 +5,7 @@ use clap::builder::ValueParser;
use uucore::error::{UResult, USimpleError, UUsageError};
use uucore::{display::Quotable, format_usage, help_about, help_usage, show_error, show_warning};
use clap::{Arg, ArgAction, Command};
use clap::{crate_version, Arg, ArgAction, Command};
use selinux::{OpaqueSecurityContext, SecurityContext};
use std::borrow::Cow;
@ -19,7 +19,6 @@ mod fts;
use errors::*;
const VERSION: &str = env!("CARGO_PKG_VERSION");
const ABOUT: &str = help_about!("chcon.md");
const USAGE: &str = help_usage!("chcon.md");
@ -146,7 +145,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(VERSION)
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)

10
src/uu/chgrp/chgrp.md Normal file
View file

@ -0,0 +1,10 @@
<!-- spell-checker:ignore (vars) RFILE -->
# chgrp
```
chgrp [OPTION]... GROUP FILE...
[OPTION]... --reference=RFILE FILE...
```
Change the group of each FILE to GROUP.

View file

@ -10,20 +10,16 @@
use uucore::display::Quotable;
pub use uucore::entries;
use uucore::error::{FromIo, UResult, USimpleError};
use uucore::format_usage;
use uucore::perms::{chown_base, options, IfFrom};
use uucore::{format_usage, help_about, help_usage};
use clap::{Arg, ArgAction, ArgMatches, Command};
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
use std::fs;
use std::os::unix::fs::MetadataExt;
static ABOUT: &str = "Change the group of each FILE to GROUP.";
static VERSION: &str = env!("CARGO_PKG_VERSION");
const USAGE: &str = "\
{} [OPTION]... GROUP FILE...\n \
{} [OPTION]... --reference=RFILE FILE...";
static ABOUT: &str = help_about!("chgrp.md");
const USAGE: &str = help_usage!("chgrp.md");
fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option<u32>, Option<u32>, IfFrom)> {
let dest_gid = if let Some(file) = matches.get_one::<String>(options::REFERENCE) {
@ -59,7 +55,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(VERSION)
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)

16
src/uu/chmod/chmod.md Normal file
View file

@ -0,0 +1,16 @@
<!-- spell-checker:ignore RFILE ugoa -->
# chmod
```
chmod [OPTION]... MODE[,MODE]... FILE...
chmod [OPTION]... OCTAL-MODE FILE...
chmod [OPTION]... --reference=RFILE FILE...
```
Change the mode of each FILE to MODE.
With --reference, change the mode of each FILE to that of RFILE.
## After Help
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.

View file

@ -17,16 +17,11 @@ use uucore::fs::display_permissions_unix;
use uucore::libc::mode_t;
#[cfg(not(windows))]
use uucore::mode;
use uucore::{format_usage, show, show_error};
use uucore::{format_usage, help_about, help_section, help_usage, show, show_error};
const ABOUT: &str = "Change the mode of each FILE to MODE.\n\
With --reference, change the mode of each FILE to that of RFILE.";
const USAGE: &str = "\
{} [OPTION]... MODE[,MODE]... FILE...
{} [OPTION]... OCTAL-MODE FILE...
{} [OPTION]... --reference=RFILE FILE...";
const LONG_USAGE: &str =
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.";
const ABOUT: &str = help_about!("chmod.md");
const USAGE: &str = help_usage!("chmod.md");
const LONG_USAGE: &str = help_section!("after help", "chmod.md");
mod options {
pub const CHANGES: &str = "changes";

8
src/uu/chroot/chroot.md Normal file
View file

@ -0,0 +1,8 @@
<!-- spell-checker:ignore NEWROOT -->
# chroot
```
chroot [OPTION]... NEWROOT [COMMAND [ARG]...]
```
Run COMMAND with root directory set to NEWROOT.

View file

@ -19,10 +19,10 @@ use std::process;
use uucore::error::{set_exit_code, UClapError, UResult, UUsageError};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
use uucore::{entries, format_usage};
use uucore::{entries, format_usage, help_about, help_usage};
static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT.";
static USAGE: &str = "{} [OPTION]... NEWROOT [COMMAND [ARG]...]";
static ABOUT: &str = help_about!("chroot.md");
static USAGE: &str = help_usage!("chroot.md");
mod options {
pub const NEWROOT: &str = "newroot";

View file

@ -1,18 +1,18 @@
<!-- markdownlint-disable first-line-heading -->
<!-- spell-checker:ignore (markdown) markdownlint -->
## Feature list
# Feature list
<!-- spell-checker:ignore (options) linkgs reflink -->
### To Do
## To Do
- [ ] cli-symbolic-links
- [ ] context
- [ ] copy-contents
- [ ] sparse
### Completed
## Completed
- [x] archive
- [x] attributes-only

View file

@ -26,7 +26,7 @@ use std::os::unix::fs::{FileTypeExt, PermissionsExt};
use std::path::{Path, PathBuf, StripPrefixError};
use std::string::ToString;
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
use clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgMatches, Command};
use filetime::FileTime;
use indicatif::{ProgressBar, ProgressStyle};
#[cfg(unix)]
@ -91,7 +91,7 @@ quick_error! {
/// Invalid arguments to backup
Backup(description: String) { display("{}\nTry '{} --help' for more information.", description, uucore::execution_phrase()) }
NotADirectory(path: String) { display("'{}' is not a directory", path) }
NotADirectory(path: PathBuf) { display("'{}' is not a directory", path.display()) }
}
}
@ -222,7 +222,7 @@ pub struct Options {
attributes: Attributes,
recursive: bool,
backup_suffix: String,
target_dir: Option<String>,
target_dir: Option<PathBuf>,
update: bool,
verbose: bool,
progress_bar: bool,
@ -302,6 +302,7 @@ pub fn uu_app() -> Command {
.long(options::TARGET_DIRECTORY)
.value_name(options::TARGET_DIRECTORY)
.value_hint(clap::ValueHint::DirPath)
.value_parser(ValueParser::path_buf())
.help("copy all SOURCE arguments into target-directory"),
)
.arg(
@ -555,7 +556,8 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::PATHS)
.action(ArgAction::Append)
.value_hint(clap::ValueHint::AnyPath),
.value_hint(clap::ValueHint::AnyPath)
.value_parser(ValueParser::path_buf()),
)
}
@ -576,7 +578,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
clap::error::ErrorKind::DisplayVersion => print!("{}", app.render_version()),
_ => return Err(Box::new(e.with_exit_code(1))),
};
} else if let Ok(matches) = matches {
} else if let Ok(mut matches) = matches {
let options = Options::from_matches(&matches)?;
if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup {
@ -586,12 +588,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
));
}
let paths: Vec<String> = matches
.get_many::<String>(options::PATHS)
.map(|v| v.map(ToString::to_string).collect())
let paths: Vec<PathBuf> = matches
.remove_many::<PathBuf>(options::PATHS)
.map(|v| v.collect())
.unwrap_or_default();
let (sources, target) = parse_path_args(&paths, &options)?;
let (sources, target) = parse_path_args(paths, &options)?;
if let Err(error) = copy(&sources, &target, &options) {
match error {
@ -754,11 +756,11 @@ impl Options {
// Parse target directory options
let no_target_dir = matches.get_flag(options::NO_TARGET_DIRECTORY);
let target_dir = matches
.get_one::<String>(options::TARGET_DIRECTORY)
.map(ToString::to_string);
.get_one::<PathBuf>(options::TARGET_DIRECTORY)
.cloned();
if let Some(dir) = &target_dir {
if !Path::new(dir).is_dir() {
if !dir.is_dir() {
return Err(Error::NotADirectory(dir.clone()));
}
};
@ -915,9 +917,7 @@ impl TargetType {
}
/// Returns tuple of (Source paths, Target)
fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<Source>, Target)> {
let mut paths = path_args.iter().map(PathBuf::from).collect::<Vec<_>>();
fn parse_path_args(mut paths: Vec<Source>, options: &Options) -> CopyResult<(Vec<Source>, Target)> {
if paths.is_empty() {
// No files specified
return Err("missing file operand".into());
@ -933,7 +933,7 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
Some(ref target) => {
// All path args are sources, and the target dir was
// specified separately
PathBuf::from(target)
target.clone()
}
None => {
// If there was no explicit target-dir, then use the last

View file

@ -1,46 +1,45 @@
## Benchmarking cut
# Benchmarking cut
### Performance profile
## Performance profile
In normal use cases a significant amount of the total execution time of `cut`
is spent performing I/O. When invoked with the `-f` option (cut fields) some
CPU time is spent on detecting fields (in `Searcher::next`). Other than that
some small amount of CPU time is spent on breaking the input stream into lines.
### How to
## How to
When fixing bugs or adding features you might want to compare
performance before and after your code changes.
- `hyperfine` can be used to accurately measure and compare the total
- `hyperfine` can be used to accurately measure and compare the total
execution time of one or more commands.
```
$ cargo build --release --package uu_cut
```shell
cargo build --release --package uu_cut
$ hyperfine -w3 "./target/release/cut -f2-4,8 -d' ' input.txt" "cut -f2-4,8 -d' ' input.txt"
hyperfine -w3 "./target/release/cut -f2-4,8 -d' ' input.txt" "cut -f2-4,8 -d' ' input.txt"
```
You can put those two commands in a shell script to be sure that you don't
forget to build after making any changes.
When optimizing or fixing performance regressions seeing the number of times a
function is called, and the amount of time it takes can be useful.
- `cargo flamegraph` generates flame graphs from function level metrics it records using `perf` or `dtrace`
- `cargo flamegraph` generates flame graphs from function level metrics it records using `perf` or `dtrace`
```
$ cargo flamegraph --bin cut --package uu_cut -- -f1,3-4 input.txt > /dev/null
```shell
cargo flamegraph --bin cut --package uu_cut -- -f1,3-4 input.txt > /dev/null
```
### What to benchmark
## What to benchmark
There are four different performance paths in `cut` to benchmark.
- Byte ranges `-c`/`--characters` or `-b`/`--bytes` e.g. `cut -c 2,4,6-`
- Byte ranges with output delimiters e.g. `cut -c 4- --output-delimiter=/`
- Fields e.g. `cut -f -4`
- Fields with output delimiters e.g. `cut -f 7-10 --output-delimiter=:`
- Byte ranges `-c`/`--characters` or `-b`/`--bytes` e.g. `cut -c 2,4,6-`
- Byte ranges with output delimiters e.g. `cut -c 4- --output-delimiter=/`
- Fields e.g. `cut -f -4`
- Fields with output delimiters e.g. `cut -f 7-10 --output-delimiter=:`
Choose a test input file with large number of lines so that program startup time does not significantly affect the benchmark.

10
src/uu/date/date.md Normal file
View file

@ -0,0 +1,10 @@
<!-- spell-checker:ignore Dhhmm -->
# date
```
date [OPTION]... [+FORMAT]...
date [OPTION]... [MMDDhhmm[[CC]YY][.ss]]
```
Print or set the system date and time

View file

@ -22,7 +22,7 @@ use uucore::display::Quotable;
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
use uucore::error::FromIo;
use uucore::error::{UResult, USimpleError};
use uucore::{format_usage, show_error};
use uucore::{format_usage, help_about, help_usage, show_error};
#[cfg(windows)]
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
@ -36,10 +36,8 @@ const MINUTE: &str = "minute";
const SECOND: &str = "second";
const NS: &str = "ns";
const ABOUT: &str = "Print or set the system date and time";
const USAGE: &str = "\
{} [OPTION]... [+FORMAT]...
{} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]";
const ABOUT: &str = help_about!("date.md");
const USAGE: &str = help_usage!("date.md");
const OPT_DATE: &str = "date";
const OPT_FORMAT: &str = "format";

View file

@ -45,7 +45,7 @@ be roughly equivalent to the total bytes copied (`blocksize` x `count`).
Some useful invocations for testing would be the following:
```
```shell
hyperfine "./target/release/dd bs=4k count=1000000 < /dev/zero > /dev/null"
hyperfine "./target/release/dd bs=1M count=20000 < /dev/zero > /dev/null"
hyperfine "./target/release/dd bs=1G count=10 < /dev/zero > /dev/null"
@ -57,7 +57,7 @@ Typically you would choose a small blocksize for measuring the performance of
typically does some set amount of work per block which only depends on the size
of the block if conversions are used.
As an example, https://github.com/uutils/coreutils/pull/3600 made a change to
As an example, <https://github.com/uutils/coreutils/pull/3600> made a change to
reuse the same buffer between block copies, avoiding the need to reallocate a
new block of memory for each copy. The impact of that change mostly had an
impact on large block size copies because those are the circumstances where the

View file

@ -1,6 +1,7 @@
<!-- spell-checker:ignore convs iseek oseek -->
# dd
<!-- spell-checker:ignore convs iseek oseek -->
```
dd [OPERAND]...
dd OPTION
@ -10,117 +11,116 @@ Copy, and optionally convert, a file system resource
## After Help
OPERANDS:
### Operands
bs=BYTES read and write up to BYTES bytes at a time (default: 512);
overwrites ibs and obs.
cbs=BYTES the 'conversion block size' in bytes. Applies to
the conv=block, and conv=unblock operations.
conv=CONVS a comma-separated list of conversion options or
(for legacy reasons) file flags.
count=N stop reading input after N ibs-sized read operations rather
than proceeding until EOF. See iflag=count_bytes if stopping
after N bytes is preferred
ibs=N the size of buffer used for reads (default: 512)
if=FILE the file used for input. When not specified, stdin is used instead
iflag=FLAGS a comma-separated list of input flags which specify how the input
source is treated. FLAGS may be any of the input-flags or
general-flags specified below.
skip=N (or iseek=N) skip N ibs-sized records into input before beginning
copy/convert operations. See iflag=seek_bytes if seeking N bytes
is preferred.
obs=N the size of buffer used for writes (default: 512)
of=FILE the file used for output. When not specified, stdout is used
instead
oflag=FLAGS comma separated list of output flags which specify how the output
source is treated. FLAGS may be any of the output flags or
general flags specified below
seek=N (or oseek=N) seeks N obs-sized records into output before
beginning copy/convert operations. See oflag=seek_bytes if
seeking N bytes is preferred
status=LEVEL controls whether volume and performance stats are written to
stderr.
When unspecified, dd will print stats upon completion. An example is below.
6+0 records in
16+0 records out
8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00057009 s, 14.4 MB/s
The first two lines are the 'volume' stats and the final line is
the 'performance' stats.
The volume stats indicate the number of complete and partial
ibs-sized reads, or obs-sized writes that took place during the
copy. The format of the volume stats is
<complete>+<partial>. If records have been truncated (see
conv=block), the volume stats will contain the number of
truncated records.
Possible LEVEL values are:
progress: Print periodic performance stats as the copy
proceeds.
noxfer: Print final volume stats, but not performance stats.
none: Do not print any stats.
- `Bs=BYTES` : read and write up to BYTES bytes at a time (default: 512);
overwrites `ibs` and `obs`.
- `cbs=BYTES` : the 'conversion block size' in bytes. Applies to the
`conv=block`, and `conv=unblock` operations.
- `conv=CONVS` : a comma-separated list of conversion options or (for legacy
reasons) file flags.
- `count=N` : stop reading input after N ibs-sized read operations rather
than proceeding until EOF. See `iflag=count_bytes` if stopping after N bytes
is preferred
- `ibs=N` : the size of buffer used for reads (default: 512)
- `if=FILE` : the file used for input. When not specified, stdin is used instead
- `iflag=FLAGS` : a comma-separated list of input flags which specify how the
input source is treated. FLAGS may be any of the input-flags or general-flags
specified below.
- `skip=N` (or `iseek=N`) : skip N ibs-sized records into input before beginning
copy/convert operations. See iflag=seek_bytes if seeking N bytes is preferred.
- `obs=N` : the size of buffer used for writes (default: 512)
- `of=FILE` : the file used for output. When not specified, stdout is used
instead
- `oflag=FLAGS` : comma separated list of output flags which specify how the
output source is treated. FLAGS may be any of the output flags or general
flags specified below
- `seek=N` (or `oseek=N`) : seeks N obs-sized records into output before
beginning copy/convert operations. See oflag=seek_bytes if seeking N bytes is
preferred
- `status=LEVEL` : controls whether volume and performance stats are written to
stderr.
Printing performance stats is also triggered by the INFO signal
(where supported), or the USR1 signal. Setting the
POSIXLY_CORRECT environment variable to any value (including an
empty value) will cause the USR1 signal to be ignored.
When unspecified, dd will print stats upon completion. An example is below.
CONVERSION OPTIONS:
```plain
6+0 records in
16+0 records out
8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00057009 s,
14.4 MB/s
```
ascii convert from EBCDIC to ASCII. This is the inverse of the 'ebcdic'
option. Implies conv=unblock.
ebcdic convert from ASCII to EBCDIC. This is the inverse of the 'ascii'
option. Implies conv=block.
ibm convert from ASCII to EBCDIC, applying the conventions for '[', ']'
and '~' specified in POSIX. Implies conv=block.
The first two lines are the 'volume' stats and the final line is the
'performance' stats.
The volume stats indicate the number of complete and partial ibs-sized reads,
or obs-sized writes that took place during the copy. The format of the volume
stats is `<complete>+<partial>`. If records have been truncated (see
`conv=block`), the volume stats will contain the number of truncated records.
ucase convert from lower-case to upper-case
lcase converts from upper-case to lower-case.
Possible LEVEL values are:
- `progress` : Print periodic performance stats as the copy proceeds.
- `noxfer` : Print final volume stats, but not performance stats.
- `none` : Do not print any stats.
block for each newline less than the size indicated by cbs=BYTES, remove
the newline and pad with spaces up to cbs. Lines longer than cbs are
truncated.
unblock for each block of input of the size indicated by cbs=BYTES, remove
right-trailing spaces and replace with a newline character.
Printing performance stats is also triggered by the INFO signal (where supported),
or the USR1 signal. Setting the POSIXLY_CORRECT environment variable to any value
(including an empty value) will cause the USR1 signal to be ignored.
sparse attempts to seek the output when an obs-sized block consists of only
zeros.
swab swaps each adjacent pair of bytes. If an odd number of bytes is
present, the final byte is omitted.
sync pad each ibs-sided block with zeros. If 'block' or 'unblock' is
specified, pad with spaces instead.
excl the output file must be created. Fail if the output file is already
present.
nocreat the output file will not be created. Fail if the output file in not
already present.
notrunc the output file will not be truncated. If this option is not
present, output will be truncated when opened.
noerror all read errors will be ignored. If this option is not present, dd
will only ignore Error::Interrupted.
fdatasync data will be written before finishing.
fsync data and metadata will be written before finishing.
### Conversion Options
INPUT FLAGS:
- `ascii` : convert from EBCDIC to ASCII. This is the inverse of the `ebcdic`
option. Implies `conv=unblock`.
- `ebcdic` : convert from ASCII to EBCDIC. This is the inverse of the `ascii`
option. Implies `conv=block`.
- `ibm` : convert from ASCII to EBCDIC, applying the conventions for `[`, `]`
and `~` specified in POSIX. Implies `conv=block`.
count_bytes a value to count=N will be interpreted as bytes.
skip_bytes a value to skip=N will be interpreted as bytes.
fullblock wait for ibs bytes from each read. zero-length reads are still
considered EOF.
- `ucase` : convert from lower-case to upper-case.
- `lcase` : converts from upper-case to lower-case.
OUTPUT FLAGS:
- `block` : for each newline less than the size indicated by cbs=BYTES, remove
the newline and pad with spaces up to cbs. Lines longer than cbs are truncated.
- `unblock` : for each block of input of the size indicated by cbs=BYTES, remove
right-trailing spaces and replace with a newline character.
append open file in append mode. Consider setting conv=notrunc as well.
seek_bytes a value to seek=N will be interpreted as bytes.
- `sparse` : attempts to seek the output when an obs-sized block consists of
only zeros.
- `swab` : swaps each adjacent pair of bytes. If an odd number of bytes is
present, the final byte is omitted.
- `sync` : pad each ibs-sided block with zeros. If `block` or `unblock` is
specified, pad with spaces instead.
- `excl` : the output file must be created. Fail if the output file is already
present.
- `nocreat` : the output file will not be created. Fail if the output file in
not already present.
- `notrunc` : the output file will not be truncated. If this option is not
present, output will be truncated when opened.
- `noerror` : all read errors will be ignored. If this option is not present,
dd will only ignore Error::Interrupted.
- `fdatasync` : data will be written before finishing.
- `fsync` : data and metadata will be written before finishing.
GENERAL FLAGS:
### Input flags
direct use direct I/O for data.
directory fail unless the given input (if used as an iflag) or output (if used
as an oflag) is a directory.
dsync use synchronized I/O for data.
sync use synchronized I/O for data and metadata.
nonblock use non-blocking I/O.
noatime do not update access time.
nocache request that OS drop cache.
noctty do not assign a controlling tty.
nofollow do not follow system links.
- `count_bytes` : a value to `count=N` will be interpreted as bytes.
- `skip_bytes` : a value to `skip=N` will be interpreted as bytes.
- `fullblock` : wait for ibs bytes from each read. zero-length reads are still
considered EOF.
### Output flags
- `append` : open file in append mode. Consider setting conv=notrunc as well.
- `seek_bytes` : a value to seek=N will be interpreted as bytes.
### General Flags
- `Direct` : use direct I/O for data.
- `directory` : fail unless the given input (if used as an iflag) or
output (if used as an oflag) is a directory.
- `dsync` : use synchronized I/O for data.
- `sync` : use synchronized I/O for data and metadata.
- `nonblock` : use non-blocking I/O.
- `noatime` : do not update access time.
- `nocache` : request that OS drop cache.
- `noctty` : do not assign a controlling tty.
- `nofollow` : do not follow system links.

View file

@ -1,18 +1,18 @@
## How to update the internal database
# How to update the internal database
Create the test fixtures by writing the output of the GNU dircolors commands to the fixtures folder:
```
$ dircolors --print-database > /PATH_TO_COREUTILS/tests/fixtures/dircolors/internal.expected
$ dircolors --print-ls-colors > /PATH_TO_COREUTILS/tests/fixtures/dircolors/ls_colors.expected
$ dircolors -b > /PATH_TO_COREUTILS/tests/fixtures/dircolors/bash_def.expected
$ dircolors -c > /PATH_TO_COREUTILS/tests/fixtures/dircolors/csh_def.expected
```shell
dircolors --print-database > /PATH_TO_COREUTILS/tests/fixtures/dircolors/internal.expected
dircolors --print-ls-colors > /PATH_TO_COREUTILS/tests/fixtures/dircolors/ls_colors.expected
dircolors -b > /PATH_TO_COREUTILS/tests/fixtures/dircolors/bash_def.expected
dircolors -c > /PATH_TO_COREUTILS/tests/fixtures/dircolors/csh_def.expected
```
Run the tests:
```
$ cargo test --features "dircolors" --no-default-features
```shell
cargo test --features "dircolors" --no-default-features
```
Edit `/PATH_TO_COREUTILS/src/uu/dircolors/src/colors.rs` until the tests pass.

View file

@ -19,6 +19,6 @@ of 1000).
PATTERN allows some advanced exclusions. For example, the following syntaxes
are supported:
? will match only one character
* will match zero or more characters
{a,b} will match a or b
`?` will match only one character
`*` will match zero or more characters
`{a,b}` will match a or b

View file

@ -10,49 +10,44 @@ Print the value of `EXPRESSION` to standard output
## After help
Print the value of `EXPRESSION` to standard output. A blank line below
separates increasing precedence groups.
separates increasing precedence groups.
`EXPRESSION` may be:
ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2
ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0
ARG1 < ARG2 ARG1 is less than ARG2
ARG1 <= ARG2 ARG1 is less than or equal to ARG2
ARG1 = ARG2 ARG1 is equal to ARG2
ARG1 != ARG2 ARG1 is unequal to ARG2
ARG1 >= ARG2 ARG1 is greater than or equal to ARG2
ARG1 > ARG2 ARG1 is greater than ARG2
ARG1 + ARG2 arithmetic sum of ARG1 and ARG2
ARG1 - ARG2 arithmetic difference of ARG1 and ARG2
ARG1 * ARG2 arithmetic product of ARG1 and ARG2
ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2
ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2
STRING : REGEXP anchored pattern match of REGEXP in STRING
match STRING REGEXP same as STRING : REGEXP
substr STRING POS LENGTH substring of STRING, POS counted from 1
index STRING CHARS index in STRING where any CHARS is found, or 0
length STRING length of STRING
+ TOKEN interpret TOKEN as a string, even if it is a
keyword like 'match' or an operator like '/'
( EXPRESSION ) value of EXPRESSION
- `ARG1 | ARG2`: `ARG1` if it is neither null nor 0, otherwise `ARG2`
- `ARG1 & ARG2`: `ARG1` if neither argument is null or 0, otherwise 0
- `ARG1 < ARG2`: `ARG1` is less than `ARG2`
- `ARG1 <= ARG2`: `ARG1` is less than or equal to `ARG2`
- `ARG1 = ARG2`: `ARG1` is equal to `ARG2`
- `ARG1 != ARG2`: `ARG1` is unequal to `ARG2`
- `ARG1 >= ARG2`: `ARG1` is greater than or equal to `ARG2`
- `ARG1 > ARG2`: `ARG1` is greater than `ARG2`
- `ARG1 + ARG2`: arithmetic sum of `ARG1` and `ARG2`
- `ARG1 - ARG2`: arithmetic difference of `ARG1` and `ARG2`
- `ARG1 * ARG2`: arithmetic product of `ARG1` and `ARG2`
- `ARG1 / ARG2`: arithmetic quotient of `ARG1` divided by `ARG2`
- `ARG1 % ARG2`: arithmetic remainder of `ARG1` divided by `ARG2`
- `STRING : REGEXP`: anchored pattern match of `REGEXP` in `STRING`
- `match STRING REGEXP`: same as `STRING : REGEXP`
- `substr STRING POS LENGTH`: substring of `STRING`, `POS` counted from 1
- `index STRING CHARS`: index in `STRING` where any `CHARS` is found, or 0
- `length STRING`: length of `STRING`
- `+ TOKEN`: interpret `TOKEN` as a string, even if it is a keyword like `match`
or an operator like `/`
- `( EXPRESSION )`: value of `EXPRESSION`
Beware that many operators need to be escaped or quoted for shells.
Comparisons are arithmetic if both ARGs are numbers, else lexicographical.
Pattern matches return the string matched between \( and \) or null; if
\( and \) are not used, they return the number of characters matched or 0.
Exit status is `0` if `EXPRESSION` is neither null nor `0`, `1` if `EXPRESSION` is null
or `0`, `2` if `EXPRESSION` is syntactically invalid, and `3` if an error occurred.
Exit status is `0` if `EXPRESSION` is neither null nor `0`, `1` if `EXPRESSION`
is null or `0`, `2` if `EXPRESSION` is syntactically invalid, and `3` if an
error occurred.
Environment variables:
- `EXPR_DEBUG_TOKENS=1`: dump expression's tokens
- `EXPR_DEBUG_RPN=1`: dump expression represented in reverse polish notation
- `EXPR_DEBUG_SYA_STEP=1`: dump each parser step
- `EXPR_DEBUG_AST=1`: dump expression represented abstract syntax tree
- `EXPR_DEBUG_TOKENS=1`: dump expression's tokens
- `EXPR_DEBUG_RPN=1`: dump expression represented in reverse polish notation
- `EXPR_DEBUG_SYA_STEP=1`: dump each parser step
- `EXPR_DEBUG_AST=1`: dump expression represented abstract syntax tree

View file

@ -53,19 +53,19 @@ which I recommend reading if you want to add benchmarks to `factor`.
so each sample takes a very short time, minimizing variability and
maximizing the numbers of samples we can take in a given time.
2. Benchmarks are immutable (once merged in `uutils`)
1. Benchmarks are immutable (once merged in `uutils`)
Modifying a benchmark means previously-collected values cannot meaningfully
be compared, silently giving nonsensical results. If you must modify an
existing benchmark, rename it.
3. Test common cases
1. Test common cases
We are interested in overall performance, rather than specific edge-cases;
use **reproducibly-randomized inputs**, sampling from either all possible
input values or some subset of interest.
4. Use [`criterion`], `criterion::black_box`, ...
1. Use [`criterion`], `criterion::black_box`, ...
`criterion` isn't perfect, but it is also much better than ad-hoc
solutions in each benchmark.
@ -103,7 +103,7 @@ characteristics:
1. integer factoring algorithms are randomized, with large variance in
execution time ;
2. various inputs also have large differences in factoring time, that
1. various inputs also have large differences in factoring time, that
corresponds to no natural, linear ordering of the inputs.
If (1) was untrue (i.e. if execution time wasn't random), we could faithfully

View file

@ -1,9 +1,11 @@
## Benchmarking hashsum
# Benchmarking hashsum
### To bench blake2
## To bench blake2
Taken from: https://github.com/uutils/coreutils/pull/2296
Taken from: <https://github.com/uutils/coreutils/pull/2296>
With a large file:
$ hyperfine "./target/release/coreutils hashsum --b2sum large-file" "b2sum large-file"
```shell
hyperfine "./target/release/coreutils hashsum --b2sum large-file" "b2sum large-file"
```

View file

@ -5,23 +5,31 @@ GNU version of `head`, you can use a benchmarking tool like
[hyperfine][0]. On Ubuntu 18.04 or later, you can install `hyperfine` by
running
sudo apt-get install hyperfine
```shell
sudo apt-get install hyperfine
```
Next, build the `head` binary under the release profile:
cargo build --release -p uu_head
```shell
cargo build --release -p uu_head
```
Now, get a text file to test `head` on. I used the *Complete Works of
William Shakespeare*, which is in the public domain in the United States
and most other parts of the world.
wget -O shakespeare.txt https://www.gutenberg.org/files/100/100-0.txt
```shell
wget -O shakespeare.txt https://www.gutenberg.org/files/100/100-0.txt
```
This particular file has about 170,000 lines, each of which is no longer
than 96 characters:
$ wc -lL shakespeare.txt
170592 96 shakespeare.txt
```shell
$ wc -lL shakespeare.txt
170592 96 shakespeare.txt
```
You could use files of different shapes and sizes to test the
performance of `head` in different situations. For a larger file, you
@ -32,9 +40,11 @@ contains about 130 million lines.
Finally, you can compare the performance of the two versions of `head`
by running, for example,
hyperfine \
"head -n 100000 shakespeare.txt" \
"target/release/head -n 100000 shakespeare.txt"
```shell
hyperfine \
"head -n 100000 shakespeare.txt" \
"target/release/head -n 100000 shakespeare.txt"
```
[0]: https://github.com/sharkdp/hyperfine
[1]: https://www.wikidata.org/wiki/Wikidata:Database_download

View file

@ -17,11 +17,14 @@ A benchmark with `-j` and `-i` shows the following time:
| libc | 25% | I/O and memory allocation. |
More detailed profiles can be obtained via [flame graphs](https://github.com/flamegraph-rs/flamegraph):
```
```shell
cargo flamegraph --bin join --package uu_join -- file1 file2 > /dev/null
```
You may need to add the following lines to the top-level `Cargo.toml` to get full stack traces:
```
```toml
[profile.release]
debug = true
```
@ -34,22 +37,26 @@ in practice many CSV datasets will function well after being sorted.
Like most of the utils, the recommended tool for benchmarking is [hyperfine](https://github.com/sharkdp/hyperfine).
To benchmark your changes:
- checkout the main branch (without your changes), do a `--release` build, and back up the executable produced at `target/release/join`
- checkout your working branch (with your changes), do a `--release` build
- run
```
hyperfine -w 5 "/path/to/main/branch/build/join file1 file2" "/path/to/working/branch/build/join file1 file2"
```
- you'll likely need to add additional options to both commands, such as a field separator, or if you're benchmarking some particular behavior
- you can also optionally benchmark against GNU's join
- checkout the main branch (without your changes), do a `--release` build, and back up the executable produced at `target/release/join`
- checkout your working branch (with your changes), do a `--release` build
- run
```shell
hyperfine -w 5 "/path/to/main/branch/build/join file1 file2" "/path/to/working/branch/build/join file1 file2"
```
- you'll likely need to add additional options to both commands, such as a field separator, or if you're benchmarking some particular behavior
- you can also optionally benchmark against GNU's join
## What to benchmark
The following options can have a non-trivial impact on performance:
- `-a`/`-v` if one of the two files has significantly more lines than the other
- `-j`/`-1`/`-2` cause work to be done to grab the appropriate field
- `-i` adds a call to `to_ascii_lowercase()` that adds some time for allocating and dropping memory for the lowercase key
- `--nocheck-order` causes some calls of `Input::compare` to be skipped
- `-a`/`-v` if one of the two files has significantly more lines than the other
- `-j`/`-1`/`-2` cause work to be done to grab the appropriate field
- `-i` adds a call to `to_ascii_lowercase()` that adds some time for allocating and dropping memory for the lowercase key
- `--nocheck-order` causes some calls of `Input::compare` to be skipped
The content of the files being joined has a very significant impact on the performance.
Things like how long each line is, how many fields there are, how long the key fields are, how many lines there are, how many lines can be joined, and how many lines each line can be joined with all change the behavior of the hotpaths.

View file

@ -9,13 +9,13 @@ Run `cargo build --release` before benchmarking after you make a change!
## Simple recursive ls
- Get a large tree, for example linux kernel source tree.
- Benchmark simple recursive ls with hyperfine: `hyperfine --warmup 2 "target/release/coreutils ls -R tree > /dev/null"`.
- Get a large tree, for example linux kernel source tree.
- Benchmark simple recursive ls with hyperfine: `hyperfine --warmup 2 "target/release/coreutils ls -R tree > /dev/null"`.
## Recursive ls with all and long options
- Same tree as above
- Benchmark recursive ls with -al -R options with hyperfine: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null"`.
- Same tree as above
- Benchmark recursive ls with -al -R options with hyperfine: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null"`.
## Comparing with GNU ls
@ -29,7 +29,8 @@ Example: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/n
This can also be used to compare with version of ls built before your changes to ensure your change does not regress this.
Here is a `bash` script for doing this comparison:
```bash
```shell
#!/bin/bash
cargo build --no-default-features --features ls --release
args="$@"
@ -46,12 +47,14 @@ hyperfine "ls $args" "target/release/coreutils ls $args"
## Cargo Flamegraph
With Cargo Flamegraph you can easily make a flamegraph of `ls`:
```bash
```shell
cargo flamegraph --cmd coreutils -- ls [additional parameters]
```
However, if the `-R` option is given, the output becomes pretty much useless due to recursion. We can fix this by merging all the direct recursive calls with `uniq`, below is a `bash` script that does this.
```bash
```shell
#!/bin/bash
cargo build --release --no-default-features --features ls
perf record target/release/coreutils ls "$@"

12
src/uu/ls/ls.md Normal file
View file

@ -0,0 +1,12 @@
# ls
```
ls [OPTION]... [FILE]...
```
List directory contents.
Ignore files and directories starting with a '.' by default
## After help
The TIME_STYLE argument can be full-iso, long-iso, iso, locale or +FORMAT. FORMAT is interpreted like in date. Also the TIME_STYLE environment variable sets the default style to use.

View file

@ -55,17 +55,16 @@ use uucore::{
parse_size::parse_size,
version_cmp::version_cmp,
};
use uucore::{parse_glob, show, show_error, show_warning};
use uucore::{help_about, help_section, help_usage, parse_glob, show, show_error, show_warning};
#[cfg(not(feature = "selinux"))]
static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)";
#[cfg(feature = "selinux")]
static CONTEXT_HELP_TEXT: &str = "print any security context of each file";
const ABOUT: &str = r#"List directory contents.
Ignore files and directories starting with a '.' by default"#;
const USAGE: &str = "{} [OPTION]... [FILE]...";
const ABOUT: &str = help_about!("ls.md");
const AFTER_HELP: &str = help_section!("after help", "ls.md");
const USAGE: &str = help_usage!("ls.md");
pub mod options {
pub mod format {
@ -1621,10 +1620,7 @@ pub fn uu_app() -> Command {
.value_hint(clap::ValueHint::AnyPath)
.value_parser(ValueParser::os_string()),
)
.after_help(
"The TIME_STYLE argument can be full-iso, long-iso, iso, locale or +FORMAT. FORMAT is interpreted like in date. \
Also the TIME_STYLE environment variable sets the default style to use.",
)
.after_help(AFTER_HELP)
}
/// Represents a Path along with it's associated data.

View file

@ -1,6 +1,7 @@
<!-- spell-checker:ignore ugoa -->
# mkdir
<!-- spell-checker:ignore ugoa -->
```
mkdir [OPTION]... [USER]
```

View file

@ -1,6 +1,7 @@
<!-- spell-checker:ignore N'th M'th -->
# numfmt
<!-- spell-checker:ignore N'th M'th -->
```
numfmt [OPTION]... [NUMBER]...
```
@ -10,24 +11,25 @@ Convert numbers from/to human-readable strings
## After Help
`UNIT` options:
- `none`: no auto-scaling is done; suffixes will trigger an error
- `auto`: accept optional single/two letter suffix:
1K = 1000, 1Ki = 1024, 1M = 1000000, 1Mi = 1048576,
- `none`: no auto-scaling is done; suffixes will trigger an error
- `auto`: accept optional single/two letter suffix:
- `si`: accept optional single letter suffix:
1K = 1000, 1Ki = 1024, 1M = 1000000, 1Mi = 1048576,
1K = 1000, 1M = 1000000, ...
- `si`: accept optional single letter suffix:
- `iec`: accept optional single letter suffix:
1K = 1000, 1M = 1000000, ...
1K = 1024, 1M = 1048576, ...
- `iec`: accept optional single letter suffix:
1K = 1024, 1M = 1048576, ...
- `iec-i`: accept optional two-letter suffix:
1Ki = 1024, 1Mi = 1048576, ...
1Ki = 1024, 1Mi = 1048576, ...
`FIELDS` supports `cut(1)` style field ranges:
- `FIELDS` supports `cut(1)` style field ranges:
N N'th field, counted from 1
N- from N'th field, to end of line

11
src/uu/ptx/ptx.md Normal file
View file

@ -0,0 +1,11 @@
# ptx
```
ptx [OPTION]... [INPUT]...
ptx -G [OPTION]... [INPUT [OUTPUT]]
```
Produce a permuted index of file contents
Output a permuted index, including context, of the words in the input files.
Mandatory arguments to long options are mandatory for short options too.
With no FILE, or when FILE is -, read standard input. Default is '-F /'.

View file

@ -19,16 +19,10 @@ use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
use std::num::ParseIntError;
use uucore::display::Quotable;
use uucore::error::{FromIo, UError, UResult};
use uucore::format_usage;
use uucore::{format_usage, help_about, help_usage};
const USAGE: &str = "\
{} [OPTION]... [INPUT]...
{} -G [OPTION]... [INPUT [OUTPUT]]";
const ABOUT: &str = "\
Output a permuted index, including context, of the words in the input files. \n\n\
Mandatory arguments to long options are mandatory for short options too.\n\
With no FILE, or when FILE is -, read standard input. Default is '-F /'.";
const USAGE: &str = help_usage!("ptx.md");
const ABOUT: &str = help_about!("ptx.md");
const REGEX_CHARCLASS: &str = "^-]\\";

7
src/uu/pwd/pwd.md Normal file
View file

@ -0,0 +1,7 @@
# pwd
```
pwd [OPTION]... [FILE]...
```
Display the full filename of the current working directory.

View file

@ -10,13 +10,13 @@ use clap::{crate_version, Arg, Command};
use std::env;
use std::io;
use std::path::PathBuf;
use uucore::format_usage;
use uucore::{format_usage, help_about, help_usage};
use uucore::display::println_verbatim;
use uucore::error::{FromIo, UResult};
static ABOUT: &str = "Display the full filename of the current working directory.";
const USAGE: &str = "{} [OPTION]... FILE...";
static ABOUT: &str = help_about!("pwd.md");
const USAGE: &str = help_usage!("pwd.md");
static OPT_LOGICAL: &str = "logical";
static OPT_PHYSICAL: &str = "physical";

View file

@ -4,4 +4,4 @@
realpath [OPTION]... FILE...
```
Print the resolved path
Print the resolved path

View file

@ -3,7 +3,7 @@
use clap::builder::ValueParser;
use uucore::error::{UResult, UUsageError};
use clap::{Arg, ArgAction, Command};
use clap::{crate_version, Arg, ArgAction, Command};
use selinux::{OpaqueSecurityContext, SecurityClass, SecurityContext};
use uucore::format_usage;
@ -18,7 +18,6 @@ mod errors;
use errors::error_exit_status;
use errors::{Error, Result, RunconError};
const VERSION: &str = env!("CARGO_PKG_VERSION");
const ABOUT: &str = "Run command with specified security context.";
const USAGE: &str = "\
{} [CONTEXT COMMAND [ARG...]]
@ -107,7 +106,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(VERSION)
.version(crate_version!())
.about(ABOUT)
.after_help(DESCRIPTION)
.override_usage(format_usage(USAGE))

View file

@ -5,15 +5,21 @@ GNU version of `seq`, you can use a benchmarking tool like
[hyperfine][0]. On Ubuntu 18.04 or later, you can install `hyperfine` by
running
sudo apt-get install hyperfine
```shell
sudo apt-get install hyperfine
```
Next, build the `seq` binary under the release profile:
cargo build --release -p uu_seq
```shell
cargo build --release -p uu_seq
```
Finally, you can compare the performance of the two versions of `head`
by running, for example,
hyperfine "seq 1000000" "target/release/seq 1000000"
```shell
hyperfine "seq 1000000" "target/release/seq 1000000"
```
[0]: https://github.com/sharkdp/hyperfine

View file

@ -21,10 +21,10 @@ To avoid distortions from IO, it is recommended to store input data in tmpfs.
## Without repetition
By default, `shuf` samples without repetition.
By default, `shuf` samples without repetition.
To benchmark only the randomization and not IO, we can pass the `-i` flag with
a range of numbers to randomly sample from. An example of a command that works
To benchmark only the randomization and not IO, we can pass the `-i` flag with
a range of numbers to randomly sample from. An example of a command that works
well for testing:
```shell

View file

@ -8,4 +8,4 @@ shuf -i LO-HI [OPTION]...;
Shuffle the input by outputting a random permutation of input lines.
Each output permutation is equally likely.
With no FILE, or when FILE is -, read standard input.
With no FILE, or when FILE is -, read standard input.

View file

@ -13,4 +13,4 @@ Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default),
'm' for minutes, 'h' for hours or 'd' for days. Unlike most implementations
that require NUMBER be an integer, here NUMBER may be an arbitrary floating
point number. Given two or more arguments, pause for the amount of time
specified by the sum of their values.
specified by the sum of their values.

View file

@ -12,64 +12,59 @@ Run `cargo build --release` before benchmarking after you make a change!
## Sorting a wordlist
- Get a wordlist, for example with [words](<https://en.wikipedia.org/wiki/Words_(Unix)>) on Linux. The exact wordlist
- Get a wordlist, for example with [words](<https://en.wikipedia.org/wiki/Words_(Unix)>) on Linux. The exact wordlist
doesn't matter for performance comparisons. In this example I'm using `/usr/share/dict/american-english` as the wordlist.
- Shuffle the wordlist by running `sort -R /usr/share/dict/american-english > shuffled_wordlist.txt`.
- Benchmark sorting the wordlist with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -o output.txt"`.
- Shuffle the wordlist by running `sort -R /usr/share/dict/american-english > shuffled_wordlist.txt`.
- Benchmark sorting the wordlist with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -o output.txt"`.
## Sorting a wordlist with ignore_case
- Same wordlist as above
- Benchmark sorting the wordlist ignoring the case with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -f -o output.txt"`.
- Same wordlist as above
- Benchmark sorting the wordlist ignoring the case with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -f -o output.txt"`.
## Sorting numbers
- Generate a list of numbers: `seq 0 100000 | sort -R > shuffled_numbers.txt`.
- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -n -o output.txt"`.
- Generate a list of numbers: `seq 0 100000 | sort -R > shuffled_numbers.txt`.
- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -n -o output.txt"`.
## Sorting numbers with -g
- Same list of numbers as above.
- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -g -o output.txt"`.
- Same list of numbers as above.
- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -g -o output.txt"`.
## Sorting numbers with SI prefixes
- Generate a list of numbers:
<details>
<summary>Rust script</summary>
- Generate a list of numbers:
## Cargo.toml
## Cargo.toml
```toml
[dependencies]
rand = "0.8.3"
```
```toml
[dependencies]
rand = "0.8.3"
```
## main.rs
## main.rs
```rust
use rand::prelude::*;
fn main() {
let suffixes = ['k', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
let mut rng = thread_rng();
for _ in 0..100000 {
println!(
"{}{}",
rng.gen_range(0..1000000),
suffixes.choose(&mut rng).unwrap()
)
}
```rust
use rand::prelude::*;
fn main() {
let suffixes = ['k', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
let mut rng = thread_rng();
for _ in 0..100000 {
println!(
"{}{}",
rng.gen_range(0..1000000),
suffixes.choose(&mut rng).unwrap()
)
}
}
```
```
## running
## running
`cargo run > shuffled_numbers_si.txt`
`cargo run > shuffled_numbers_si.txt`
</details>
- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers_si.txt -h -o output.txt"`.
- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers_si.txt -h -o output.txt"`.
## External sorting
@ -83,28 +78,28 @@ Example: Run `hyperfine './target/release/coreutils sort shuffled_wordlist.txt -
"Merge" sort merges already sorted files. It is a sub-step of external sorting, so benchmarking it separately may be helpful.
- Splitting `shuffled_wordlist.txt` can be achieved by running `split shuffled_wordlist.txt shuffled_wordlist_slice_ --additional-suffix=.txt`
- Sort each part by running `for f in shuffled_wordlist_slice_*; do sort $f -o $f; done`
- Benchmark merging by running `hyperfine "target/release/coreutils sort -m shuffled_wordlist_slice_*"`
- Splitting `shuffled_wordlist.txt` can be achieved by running `split shuffled_wordlist.txt shuffled_wordlist_slice_ --additional-suffix=.txt`
- Sort each part by running `for f in shuffled_wordlist_slice_*; do sort $f -o $f; done`
- Benchmark merging by running `hyperfine "target/release/coreutils sort -m shuffled_wordlist_slice_*"`
## Check
When invoked with -c, we simply check if the input is already ordered. The input for benchmarking should be an already sorted file.
- Benchmark checking by running `hyperfine "target/release/coreutils sort -c sorted_wordlist.txt"`
- Benchmark checking by running `hyperfine "target/release/coreutils sort -c sorted_wordlist.txt"`
## Stdout and stdin performance
Try to run the above benchmarks by piping the input through stdin (standard input) and redirect the
output through stdout (standard output):
- Remove the input file from the arguments and add `cat [input_file] | ` at the beginning.
- Remove `-o output.txt` and add `> output.txt` at the end.
- Remove the input file from the arguments and add ```cat [input_file] |``` at the beginning.
- Remove `-o output.txt` and add `> output.txt` at the end.
Example: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -n -o output.txt"` becomes
`hyperfine "cat shuffled_numbers.txt | target/release/coreutils sort -n > output.txt`
- Check that performance is similar to the original benchmark.
- Check that performance is similar to the original benchmark.
## Comparing with GNU sort
@ -121,37 +116,34 @@ The above benchmarks use hyperfine to measure the speed of sorting. There are ho
resource usage. One way to measure them is the `time` command. This is not to be confused with the `time` that is built in to the bash shell.
You may have to install `time` first, then you have to run it with `/bin/time -v` to give it precedence over the built in `time`.
<details>
<summary>Example output</summary>
Command being timed: "target/release/coreutils sort shuffled_numbers.txt"
User time (seconds): 0.10
System time (seconds): 0.00
Percent of CPU this job got: 365%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.02
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 25360
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 5802
Voluntary context switches: 462
Involuntary context switches: 73
Swaps: 0
File system inputs: 1184
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
</details>
```plain
Command being timed: "target/release/coreutils sort shuffled_numbers.txt"
User time (seconds): 0.10
System time (seconds): 0.00
Percent of CPU this job got: 365%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.02
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 25360
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 5802
Voluntary context switches: 462
Involuntary context switches: 73
Swaps: 0
File system inputs: 1184
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
```
Useful metrics to look at could be:
- User time
- Percent of CPU this job got
- Maximum resident set size
- User time
- Percent of CPU this job got
- Maximum resident set size

View file

@ -7,11 +7,15 @@ GNU version of `split`, you can use a benchmarking tool like
[hyperfine][0]. On Ubuntu 18.04 or later, you can install `hyperfine` by
running
sudo apt-get install hyperfine
```
sudo apt-get install hyperfine
```
Next, build the `split` binary under the release profile:
cargo build --release -p uu_split
```
cargo build --release -p uu_split
```
Now, get a text file to test `split` on. The `split` program has three
main modes of operation: chunk by lines, chunk by bytes, and chunk by
@ -21,7 +25,9 @@ operation. For example, to test chunking by bytes on a large input file,
you can create a file named `testfile.txt` containing one million null
bytes like this:
printf "%0.s\0" {1..1000000} > testfile.txt
```
printf "%0.s\0" {1..1000000} > testfile.txt
```
For another example, to test chunking by bytes on a large real-world
input file, you could download a [database dump of Wikidata][1] or some
@ -31,10 +37,12 @@ file][2] contains about 130 million lines.
Finally, you can compare the performance of the two versions of `split`
by running, for example,
cd /tmp && hyperfine \
--prepare 'rm x* || true' \
"split -b 1000 testfile.txt" \
"target/release/split -b 1000 testfile.txt"
```
cd /tmp && hyperfine \
--prepare 'rm x* || true' \
"split -b 1000 testfile.txt" \
"target/release/split -b 1000 testfile.txt"
```
Since `split` creates a lot of files on the filesystem, I recommend
changing to the `/tmp` directory before running the benchmark. The

View file

@ -4,7 +4,8 @@
### Flags
* [ ] `--verbose` - created file printing is implemented, don't know if there is anything else
* [ ] `--verbose` - created file printing is implemented, don't know
if there is anything else
## Possible Optimizations

View file

@ -8,53 +8,53 @@ Display file or file system status.
## Long Usage
The valid format sequences for files (without `--file-system`):
Valid format sequences for files (without `--file-system`):
%a access rights in octal (note '#' and '0' printf flags)
%A access rights in human readable form
%b number of blocks allocated (see %B)
%B the size in bytes of each block reported by %b
%C SELinux security context string
%d device number in decimal
%D device number in hex
%f raw mode in hex
%F file type
%g group ID of owner
%G group name of owner
%h number of hard links
%i inode number
%m mount point
%n file name
%N quoted file name with dereference if symbolic link
%o optimal I/O transfer size hint
%s total size, in bytes
%t major device type in hex, for character/block device special files
%T minor device type in hex, for character/block device special files
%u user ID of owner
%U user name of owner
%w time of file birth, human-readable; - if unknown
%W time of file birth, seconds since Epoch; 0 if unknown
%x time of last access, human-readable
%X time of last access, seconds since Epoch
%y time of last data modification, human-readable
%Y time of last data modification, seconds since Epoch
%z time of last status change, human-readable
%Z time of last status change, seconds since Epoch
- `%a`: access rights in octal (note '#' and '0' printf flags)
- `%A`: access rights in human readable form
- `%b`: number of blocks allocated (see %B)
- `%B`: the size in bytes of each block reported by %b
- `%C`: SELinux security context string
- `%d`: device number in decimal
- `%D`: device number in hex
- `%f`: raw mode in hex
- `%F`: file type
- `%g`: group ID of owner
- `%G`: group name of owner
- `%h`: number of hard links
- `%i`: inode number
- `%m`: mount point
- `%n`: file name
- `%N`: quoted file name with dereference if symbolic link
- `%o`: optimal I/O transfer size hint
- `%s`: total size, in bytes
- `%t`: major device type in hex, for character/block device special files
- `%T`: minor device type in hex, for character/block device special files
- `%u`: user ID of owner
- `%U`: user name of owner
- `%w`: time of file birth, human-readable; - if unknown
- `%W`: time of file birth, seconds since Epoch; 0 if unknown
- `%x`: time of last access, human-readable
- `%X`: time of last access, seconds since Epoch
- `%y`: time of last data modification, human-readable
- `%Y`: time of last data modification, seconds since Epoch
- `%z`: time of last status change, human-readable
- `%Z`: time of last status change, seconds since Epoch
Valid format sequences for file systems:
%a free blocks available to non-superuser
%b total data blocks in file system
%c total file nodes in file system
%d free file nodes in file system
%f free blocks in file system
%i file system ID in hex
%l maximum length of filenames
%n file name
%s block size (for faster transfers)
%S fundamental block size (for block counts)
%t file system type in hex
%T file system type in human readable form
- `%a`: free blocks available to non-superuser
- `%b`: total data blocks in file system
- `%c`: total file nodes in file system
- `%d`: free file nodes in file system
- `%f`: free blocks in file system
- `%i`: file system ID in hex
- `%l`: maximum length of filenames
- `%n`: file name
- `%s`: block size (for faster transfers)
- `%S`: fundamental block size (for block counts)
- `%t`: file system type in hex
- `%T`: file system type in human readable form
NOTE: your shell may have its own version of stat, which usually supersedes
the version described here. Please refer to your shell's documentation

View file

@ -1,4 +1,4 @@
## Benchmarking `sum`
# Benchmarking `sum`
<!-- spell-checker:ignore wikidatawiki -->
@ -7,17 +7,17 @@ Large sample files can for example be found in the [Wikipedia database dumps](ht
After you have obtained and uncompressed such a file, you need to build `sum` in release mode
```shell
$ cargo build --release --package uu_sum
cargo build --release --package uu_sum
```
and then you can time how it long it takes to checksum the file by running
```shell
$ time ./target/release/sum wikidatawiki-20211001-pages-logging.xml
time ./target/release/sum wikidatawiki-20211001-pages-logging.xml
```
For more systematic measurements that include warm-ups, repetitions and comparisons, [Hyperfine](https://github.com/sharkdp/hyperfine) can be helpful. For example, to compare this implementation to the one provided by your distribution run
```shell
$ hyperfine "./target/release/sum wikidatawiki-20211001-pages-logging.xml" "sum wikidatawiki-20211001-pages-logging.xml"
hyperfine "./target/release/sum wikidatawiki-20211001-pages-logging.xml" "sum wikidatawiki-20211001-pages-logging.xml"
```

View file

@ -1,25 +1,36 @@
## Benchmarking `tac`
# Benchmarking `tac`
<!-- spell-checker:ignore wikidatawiki -->
`tac` is often used to process log files in reverse chronological order, i.e. from newer towards older entries. In this case, the performance target to yield results as fast as possible, i.e. without reading in the whole file that is to be reversed line-by-line. Therefore, a sensible benchmark is to read a large log file containing N lines and measure how long it takes to produce the last K lines from that file.
`tac` is often used to process log files in reverse chronological order, i.e.
from newer towards older entries. In this case, the performance target to yield
results as fast as possible, i.e. without reading in the whole file that is to
be reversed line-by-line. Therefore, a sensible benchmark is to read a large log
file containing N lines and measure how long it takes to produce the last K
lines from that file.
Large text files can for example be found in the [Wikipedia database dumps](https://dumps.wikimedia.org/wikidatawiki/latest/), usually sized at multiple gigabytes and comprising more than 100M lines.
Large text files can for example be found in the
[Wikipedia database dumps](https://dumps.wikimedia.org/wikidatawiki/latest/),
usually sized at multiple gigabytes and comprising more than 100M lines.
After you have obtained and uncompressed such a file, you need to build `tac` in release mode
After you have obtained and uncompressed such a file, you need to build `tac`
in release mode
```shell
$ cargo build --release --package uu_tac
cargo build --release --package uu_tac
```
and then you can time how it long it takes to extract the last 10M lines by running
and then you can time how it long it takes to extract the last 10M lines by
running
```shell
$ /usr/bin/time ./target/release/tac wikidatawiki-20211001-pages-logging.xml | head -n10000000 >/dev/null
/usr/bin/time ./target/release/tac wikidatawiki-20211001-pages-logging.xml | head -n10000000 >/dev/null
```
For more systematic measurements that include warm-ups, repetitions and comparisons, [Hyperfine](https://github.com/sharkdp/hyperfine) can be helpful. For example, to compare this implementation to the one provided by your distribution run
For more systematic measurements that include warm-ups, repetitions and comparisons,
[Hyperfine](https://github.com/sharkdp/hyperfine) can be helpful.
For example, to compare this implementation to the one provided by your distribution run
```shell
$ hyperfine "./target/release/tac wikidatawiki-20211001-pages-logging.xml | head -n10000000 >/dev/null" "/usr/bin/tac wikidatawiki-20211001-pages-logging.xml | head -n10000000 >/dev/null"
hyperfine "./target/release/tac wikidatawiki-20211001-pages-logging.xml | head -n10000000 >/dev/null" "/usr/bin/tac wikidatawiki-20211001-pages-logging.xml | head -n10000000 >/dev/null"
```

View file

@ -7,40 +7,59 @@
* `--max-unchanged-stats`
Note:
There's a stub for `--max-unchanged-stats` so GNU test-suite checks using it can run, however this flag has no functionality yet.
There's a stub for `--max-unchanged-stats` so GNU test-suite checks using it
can run, however this flag has no functionality yet.
### Platform support for `--follow` and `--retry`
The `--follow=descriptor`, `--follow=name` and `--retry` flags have very good support on Linux (inotify backend).
They work good enough on macOS/BSD (kqueue backend) with some tests failing due to differences of how kqueue works compared to inotify.
Windows support is there in theory due to ReadDirectoryChanges support by the notify-crate, however these flags are completely untested on Windows.
The `--follow=descriptor`, `--follow=name` and `--retry` flags have very good
support on Linux (inotify backend).
They work good enough on macOS/BSD (kqueue backend) with some tests failing due
to differences of how kqueue works compared to inotify.
Windows support is there in theory due to ReadDirectoryChanges support by the
notify-crate, however these flags are completely untested on Windows.
Note:
The undocumented `---disable-inotify` flag is used to disable the inotify backend to test polling.
However inotify is a Linux only backend and polling is now supported also for the other backends.
Because of this, `disable-inotify` is now an alias to the new and more versatile flag name: `--use-polling`.
The undocumented `---disable-inotify` flag is used to disable the inotify
backend to test polling.
However inotify is a Linux only backend and polling is now supported also
for the other backends.
Because of this, `disable-inotify` is now an alias to the new and more versatile
flag name: `--use-polling`.
## Possible optimizations
* Don't read the whole file if not using `-f` and input is regular file. Read in chunks from the end going backwards, reading each individual chunk forward.
* Don't read the whole file if not using `-f` and input is regular file.
Read in chunks from the end going backwards, reading each individual chunk
forward.
* Reduce number of system calls to e.g. `fstat`
* Improve resource management by adding more system calls to `inotify_rm_watch` when appropriate.
* Improve resource management by adding more system calls to `inotify_rm_watch`
when appropriate.
# GNU test-suite results (9.1.8-e08752)
The functionality for the test "gnu/tests/tail-2/follow-stdin.sh" is implemented.
It fails because it is provoking closing a file descriptor with `tail -f <&-` and as part of a workaround, Rust's stdlib reopens closed FDs as `/dev/null` which means uu_tail cannot detect this.
See also, e.g. the discussion at: https://github.com/uutils/coreutils/issues/2873
It fails because it is provoking closing a file descriptor with `tail -f <&-`
and as part of a workaround, Rust's stdlib reopens closed FDs as `/dev/null`
which means uu_tail cannot detect this.
See also, e.g. the discussion at:
<https://github.com/uutils/coreutils/issues/2873>
The functionality for the test "gnu/tests/tail-2/inotify-rotate-resources.sh" is implemented.
It fails with an error because it is using `strace` to look for calls to `inotify_add_watch` and `inotify_rm_watch`,
The functionality for the test "gnu/tests/tail-2/inotify-rotate-resources.sh"
is implemented.
It fails with an error because it is using `strace` to look for calls to
`inotify_add_watch` and `inotify_rm_watch`,
however in uu_tail these system calls are invoked from a separate thread.
If the GNU test would follow threads, i.e. use `strace -f`, this issue could be resolved.
If the GNU test would follow threads, i.e. use `strace -f`, this issue could be
resolved.
There are 5 tests which are fixed but do not (always) pass the test suite if it's run inside the CI.
There are 5 tests which are fixed but do not (always) pass the test suite
if it's run inside the CI.
The reason for this is probably related to load/scheduling on the CI test VM.
The tests in question are:
- [x] `tail-2/F-vs-rename.sh`
- [x] `tail-2/follow-name.sh`
- [x] `tail-2/inotify-rotate.sh`
- [x] `tail-2/overlay-headers.sh`
- [x] `tail-2/retry.sh`
* [x] `tail-2/F-vs-rename.sh`
* [x] `tail-2/follow-name.sh`
* [x] `tail-2/inotify-rotate.sh`
* [x] `tail-2/overlay-headers.sh`
* [x] `tail-2/retry.sh`

View file

@ -1,5 +1,6 @@
# truncate
```
truncate [OPTION]... [FILE]...
```
@ -22,4 +23,4 @@ file based on its current size:
'<' => at most
'>' => at least
'/' => round down to multiple of
'%' => round up to multiple of
'%' => round up to multiple of

View file

@ -2,45 +2,59 @@
<!-- spell-checker:ignore (words) uuwc uucat largefile somefile Mshortlines moby lwcm cmds tablefmt -->
Much of what makes wc fast is avoiding unnecessary work. It has multiple strategies, depending on which data is requested.
Much of what makes wc fast is avoiding unnecessary work. It has multiple strategies,
depending on which data is requested.
## Strategies
### Counting bytes
In the case of `wc -c` the content of the input doesn't have to be inspected at all, only the size has to be known. That enables a few optimizations.
In the case of `wc -c` the content of the input doesn't have to be inspected at all,
only the size has to be known. That enables a few optimizations.
#### File size
If it can, wc reads the file size directly. This is not interesting to benchmark, except to see if it still works. Try `wc -c largefile`.
If it can, wc reads the file size directly. This is not interesting to benchmark,
except to see if it still works. Try `wc -c largefile`.
#### `splice()`
On Linux `splice()` is used to get the input's length while discarding it directly.
The best way I've found to generate a fast input to test `splice()` is to pipe the output of uutils `cat` into it. Note that GNU `cat` is slower and therefore less suitable, and that if a file is given as its input directly (as in `wc -c < largefile`) the first strategy kicks in. Try `uucat somefile | wc -c`.
The best way I've found to generate a fast input to test `splice()` is to pipe the
output of uutils `cat` into it. Note that GNU `cat` is slower and therefore less
suitable, and that if a file is given as its input directly (as in
`wc -c < largefile`) the first strategy kicks in. Try `uucat somefile | wc -c`.
### Counting lines
In the case of `wc -l` or `wc -cl` the input doesn't have to be decoded. It's read in chunks and the `bytecount` crate is used to count the newlines.
In the case of `wc -l` or `wc -cl` the input doesn't have to be decoded. It's
read in chunks and the `bytecount` crate is used to count the newlines.
It's useful to vary the line length in the input. GNU wc seems particularly bad at short lines.
It's useful to vary the line length in the input. GNU wc seems particularly
bad at short lines.
### Processing unicode
This is the most general strategy, and it's necessary for counting words, characters, and line lengths. Individual steps are still switched on and off depending on what must be reported.
This is the most general strategy, and it's necessary for counting words,
characters, and line lengths. Individual steps are still switched on and off
depending on what must be reported.
Try varying which of the `-w`, `-m`, `-l` and `-L` flags are used. (The `-c` flag is unlikely to make a difference.)
Try varying which of the `-w`, `-m`, `-l` and `-L` flags are used.
(The `-c` flag is unlikely to make a difference.)
Passing no flags is equivalent to passing `-wcl`. That case should perhaps be given special attention as it's the default.
Passing no flags is equivalent to passing `-wcl`. That case should perhaps be
given special attention as it's the default.
## Generating files
To generate a file with many very short lines, run `yes | head -c50000000 > 25Mshortlines`.
To generate a file with many very short lines, run
`yes | head -c50000000 > 25Mshortlines`.
To get a file with less artificial contents, download a book from Project Gutenberg and concatenate it a lot of times:
To get a file with less artificial contents, download a book from
Project Gutenberg and concatenate it a lot of times:
```
```shell
wget https://www.gutenberg.org/files/2701/2701-0.txt -O moby.txt
cat moby.txt moby.txt moby.txt moby.txt > moby4.txt
cat moby4.txt moby4.txt moby4.txt moby4.txt > moby16.txt
@ -49,7 +63,7 @@ cat moby16.txt moby16.txt moby16.txt moby16.txt > moby64.txt
And get one with lots of unicode too:
```
```shell
wget https://www.gutenberg.org/files/30613/30613-0.txt -O odyssey.txt
cat odyssey.txt odyssey.txt odyssey.txt odyssey.txt > odyssey4.txt
cat odyssey4.txt odyssey4.txt odyssey4.txt odyssey4.txt > odyssey16.txt
@ -57,11 +71,14 @@ cat odyssey16.txt odyssey16.txt odyssey16.txt odyssey16.txt > odyssey64.txt
cat odyssey64.txt odyssey64.txt odyssey64.txt odyssey64.txt > odyssey256.txt
```
Finally, it's interesting to try a binary file. Look for one with `du -sh /usr/bin/* | sort -h`. On my system `/usr/bin/docker` is a good candidate as it's fairly large.
Finally, it's interesting to try a binary file. Look for one with
`du -sh /usr/bin/* | sort -h`. On my system `/usr/bin/docker` is a good
candidate as it's fairly large.
## Running benchmarks
Use [`hyperfine`](https://github.com/sharkdp/hyperfine) to compare the performance. For example, `hyperfine 'wc somefile' 'uuwc somefile'`.
Use [`hyperfine`](https://github.com/sharkdp/hyperfine) to compare the
performance. For example, `hyperfine 'wc somefile' 'uuwc somefile'`.
If you want to get fancy and exhaustive, generate a table:
@ -69,6 +86,7 @@ If you want to get fancy and exhaustive, generate a table:
|------------------------|--------------|------------------|-----------------|-------------------|
| `wc <FILE>` | 1.3965 | 1.6182 | 5.2967 | 2.2294 |
| `wc -c <FILE>` | 0.8134 | 1.2774 | 0.7732 | 0.9106 |
<!-- markdownlint-disable-next-line MD033 -->
| `uucat <FILE> | wc -c` | 2.7760 | 2.5565 | 2.3769 | 2.3982 |
| `wc -l <FILE>` | 1.1441 | 1.2854 | 2.9681 | 1.1493 |
| `wc -L <FILE>` | 2.1087 | 1.2551 | 5.4577 | 2.1490 |
@ -77,12 +95,16 @@ If you want to get fancy and exhaustive, generate a table:
| `wc -lwcmL <FILE>` | 1.1687 | 0.9169 | 4.4092 | 2.0663 |
Beware that:
- Results are fuzzy and change from run to run
- You'll often want to check versions of uutils wc against each other instead of against GNU
- You'll often want to check versions of uutils wc against each other instead
of against GNU
- This takes a lot of time to generate
- This only shows the relative speedup, not the absolute time, which may be misleading if the time is very short
- This only shows the relative speedup, not the absolute time, which may be
misleading if the time is very short
Created by the following Python script:
```python
import json
import subprocess
@ -121,4 +143,6 @@ for cmd in cmds:
table.append(row)
print(tabulate(table, [""] + files, tablefmt="github"))
```
(You may have to adjust the `bins` and `files` variables depending on your setup, and please do add other interesting cases to `cmds`.)
(You may have to adjust the `bins` and `files` variables depending on your
setup, and please do add other interesting cases to `cmds`.)

View file

@ -1,5 +1,6 @@
# wc
```
wc [OPTION]... [FILE]...
```

View file

@ -662,10 +662,10 @@ fn test_chown_recursive() {
at.mkdir_all("a/b/c");
at.mkdir("z");
at.touch(&at.plus_as_string("a/a"));
at.touch(&at.plus_as_string("a/b/b"));
at.touch(&at.plus_as_string("a/b/c/c"));
at.touch(&at.plus_as_string("z/y"));
at.touch(at.plus_as_string("a/a"));
at.touch(at.plus_as_string("a/b/b"));
at.touch(at.plus_as_string("a/b/c/c"));
at.touch(at.plus_as_string("z/y"));
let result = scene
.ucmd()

View file

@ -39,7 +39,7 @@ fn test_enter_chroot_fails() {
fn test_no_such_directory() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch(&at.plus_as_string("a"));
at.touch(at.plus_as_string("a"));
ucmd.arg("a")
.fails()

View file

@ -15,11 +15,15 @@ use std::os::unix::fs::PermissionsExt;
use std::os::windows::fs::symlink_file;
#[cfg(not(windows))]
use std::path::Path;
#[cfg(target_os = "linux")]
use std::path::PathBuf;
#[cfg(any(target_os = "linux", target_os = "android"))]
use filetime::FileTime;
#[cfg(any(target_os = "linux", target_os = "android"))]
use rlimit::Resource;
#[cfg(target_os = "linux")]
use std::ffi::OsString;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::fs as std_fs;
use std::thread::sleep;
@ -91,7 +95,7 @@ fn test_cp_existing_target() {
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
// No backup should have been created
assert!(!at.file_exists(&format!("{TEST_EXISTING_FILE}~")));
assert!(!at.file_exists(format!("{TEST_EXISTING_FILE}~")));
}
#[test]
@ -636,7 +640,7 @@ fn test_cp_backup_none() {
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert!(!at.file_exists(&format!("{TEST_HOW_ARE_YOU_SOURCE}~")));
assert!(!at.file_exists(format!("{TEST_HOW_ARE_YOU_SOURCE}~")));
}
#[test]
@ -650,7 +654,7 @@ fn test_cp_backup_off() {
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert!(!at.file_exists(&format!("{TEST_HOW_ARE_YOU_SOURCE}~")));
assert!(!at.file_exists(format!("{TEST_HOW_ARE_YOU_SOURCE}~")));
}
#[test]
@ -700,7 +704,7 @@ fn test_cp_deref() {
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK);
// unlike -P/--no-deref, we expect a file, not a link
assert!(at.file_exists(
&path_to_new_symlink
path_to_new_symlink
.clone()
.into_os_string()
.into_string()
@ -1062,7 +1066,7 @@ fn test_cp_deref_folder_to_folder() {
.join(TEST_COPY_TO_FOLDER_NEW)
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK);
assert!(at.file_exists(
&path_to_new_symlink
path_to_new_symlink
.clone()
.into_os_string()
.into_string()
@ -1225,8 +1229,8 @@ fn test_cp_archive_recursive() {
let file_2 = at.subdir.join(TEST_COPY_TO_FOLDER).join("2");
let file_2_link = at.subdir.join(TEST_COPY_TO_FOLDER).join("2.link");
at.touch(&file_1.to_string_lossy());
at.touch(&file_2.to_string_lossy());
at.touch(file_1);
at.touch(file_2);
at.symlink_file("1", &file_1_link.to_string_lossy());
at.symlink_file("2", &file_2_link.to_string_lossy());
@ -1252,18 +1256,8 @@ fn test_cp_archive_recursive() {
.run();
println!("ls dest {}", result.stdout_str());
assert!(at.file_exists(
&at.subdir
.join(TEST_COPY_TO_FOLDER_NEW)
.join("1")
.to_string_lossy()
));
assert!(at.file_exists(
&at.subdir
.join(TEST_COPY_TO_FOLDER_NEW)
.join("2")
.to_string_lossy()
));
assert!(at.file_exists(at.subdir.join(TEST_COPY_TO_FOLDER_NEW).join("1")));
assert!(at.file_exists(at.subdir.join(TEST_COPY_TO_FOLDER_NEW).join("2")));
assert!(at.is_symlink(
&at.subdir
@ -1672,7 +1666,7 @@ fn test_cp_reflink_always_override() {
let dst_path: &str = &vec![MOUNTPOINT, USERDIR, "dst"].concat();
scene.fixtures.mkdir(ROOTDIR);
scene.fixtures.mkdir(&vec![ROOTDIR, USERDIR].concat());
scene.fixtures.mkdir(vec![ROOTDIR, USERDIR].concat());
// Setup:
// Because neither `mkfs.btrfs` not btrfs `mount` options allow us to have a mountpoint owned
@ -2536,6 +2530,54 @@ fn test_src_base_dot() {
assert!(!at.dir_exists("y/x"));
}
#[cfg(target_os = "linux")]
fn non_utf8_name(suffix: &str) -> OsString {
use std::os::unix::ffi::OsStringExt;
let mut name = OsString::from_vec(vec![0xff, 0xff]);
name.push(suffix);
name
}
#[cfg(target_os = "linux")]
#[test]
fn test_non_utf8_src() {
let (at, mut ucmd) = at_and_ucmd!();
let src = non_utf8_name("src");
std::fs::File::create(at.plus(&src)).unwrap();
ucmd.args(&[src, "dest".into()])
.succeeds()
.no_stderr()
.no_stdout();
assert!(at.file_exists("dest"));
}
#[cfg(target_os = "linux")]
#[test]
fn test_non_utf8_dest() {
let (at, mut ucmd) = at_and_ucmd!();
let dest = non_utf8_name("dest");
ucmd.args(&[TEST_HELLO_WORLD_SOURCE.as_ref(), &*dest])
.succeeds()
.no_stderr()
.no_stdout();
assert!(at.file_exists(dest));
}
#[cfg(target_os = "linux")]
#[test]
fn test_non_utf8_target() {
let (at, mut ucmd) = at_and_ucmd!();
let dest = non_utf8_name("dest");
at.mkdir(&dest);
ucmd.args(&["-t".as_ref(), &*dest, TEST_HELLO_WORLD_SOURCE.as_ref()])
.succeeds()
.no_stderr()
.no_stdout();
let mut copied_file = PathBuf::from(dest);
copied_file.push(TEST_HELLO_WORLD_SOURCE);
assert!(at.file_exists(copied_file));
}
#[test]
#[cfg(not(windows))]
fn test_cp_archive_on_directory_ending_dot() {

View file

@ -28,8 +28,8 @@ fn test_install_basic() {
assert!(at.file_exists(file1));
assert!(at.file_exists(file2));
assert!(at.file_exists(&format!("{dir}/{file1}")));
assert!(at.file_exists(&format!("{dir}/{file2}")));
assert!(at.file_exists(format!("{dir}/{file1}")));
assert!(at.file_exists(format!("{dir}/{file2}")));
}
#[test]
@ -76,7 +76,7 @@ fn test_install_unimplemented_arg() {
.fails()
.stderr_contains("Unimplemented");
assert!(!at.file_exists(&format!("{dir}/{file}")));
assert!(!at.file_exists(format!("{dir}/{file}")));
}
#[test]
@ -314,7 +314,7 @@ fn test_install_target_new_file() {
.no_stderr();
assert!(at.file_exists(file));
assert!(at.file_exists(&format!("{dir}/{file}")));
assert!(at.file_exists(format!("{dir}/{file}")));
}
#[test]
@ -341,7 +341,7 @@ fn test_install_target_new_file_with_group() {
result.success();
assert!(at.file_exists(file));
assert!(at.file_exists(&format!("{dir}/{file}")));
assert!(at.file_exists(format!("{dir}/{file}")));
}
#[test]
@ -368,7 +368,7 @@ fn test_install_target_new_file_with_owner() {
result.success();
assert!(at.file_exists(file));
assert!(at.file_exists(&format!("{dir}/{file}")));
assert!(at.file_exists(format!("{dir}/{file}")));
}
#[test]
@ -447,13 +447,13 @@ fn test_install_nested_paths_copy_file() {
at.mkdir(dir1);
at.mkdir(dir2);
at.touch(&format!("{dir1}/{file1}"));
at.touch(format!("{dir1}/{file1}"));
ucmd.arg(format!("{dir1}/{file1}"))
.arg(dir2)
.succeeds()
.no_stderr();
assert!(at.file_exists(&format!("{dir2}/{file1}")));
assert!(at.file_exists(format!("{dir2}/{file1}")));
}
#[test]
@ -487,7 +487,7 @@ fn test_install_failing_omitting_directory() {
.fails()
.code_is(1)
.stderr_contains("omitting directory");
assert!(at.file_exists(&format!("{dir3}/{file1}")));
assert!(at.file_exists(format!("{dir3}/{file1}")));
// install also fails, when only one source param is given
scene
@ -785,7 +785,7 @@ fn test_install_creating_leading_dirs_with_single_source_and_target_dir() {
.succeeds()
.no_stderr();
assert!(at.file_exists(&format!("{target_dir}/{source1}")));
assert!(at.file_exists(format!("{target_dir}/{source1}")));
}
#[test]
@ -863,8 +863,8 @@ fn test_install_dir() {
assert!(at.file_exists(file1));
assert!(at.file_exists(file2));
assert!(at.file_exists(&format!("{dir}/{file1}")));
assert!(at.file_exists(&format!("{dir}/{file2}")));
assert!(at.file_exists(format!("{dir}/{file1}")));
assert!(at.file_exists(format!("{dir}/{file2}")));
}
//
// test backup functionality
@ -888,7 +888,7 @@ fn test_install_backup_short_no_args_files() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -913,7 +913,7 @@ fn test_install_backup_short_no_args_file_to_dir() {
assert!(at.file_exists(file));
assert!(at.file_exists(&expect));
assert!(at.file_exists(&format!("{expect}~")));
assert!(at.file_exists(format!("{expect}~")));
}
// Long --backup option is tested separately as it requires a slightly different
@ -938,7 +938,7 @@ fn test_install_backup_long_no_args_files() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -963,7 +963,7 @@ fn test_install_backup_long_no_args_file_to_dir() {
assert!(at.file_exists(file));
assert!(at.file_exists(&expect));
assert!(at.file_exists(&format!("{expect}~")));
assert!(at.file_exists(format!("{expect}~")));
}
#[test]
@ -988,7 +988,7 @@ fn test_install_backup_short_custom_suffix() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}{suffix}")));
assert!(at.file_exists(format!("{file_b}{suffix}")));
}
#[test]
@ -1013,7 +1013,7 @@ fn test_install_backup_short_custom_suffix_hyphen_value() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}{suffix}")));
assert!(at.file_exists(format!("{file_b}{suffix}")));
}
#[test]
@ -1038,7 +1038,7 @@ fn test_install_backup_custom_suffix_via_env() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}{suffix}")));
assert!(at.file_exists(format!("{file_b}{suffix}")));
}
#[test]
@ -1061,7 +1061,7 @@ fn test_install_backup_numbered_with_t() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}.~1~")));
assert!(at.file_exists(format!("{file_b}.~1~")));
}
#[test]
@ -1084,7 +1084,7 @@ fn test_install_backup_numbered_with_numbered() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}.~1~")));
assert!(at.file_exists(format!("{file_b}.~1~")));
}
#[test]
@ -1107,7 +1107,7 @@ fn test_install_backup_existing() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -1130,7 +1130,7 @@ fn test_install_backup_nil() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -1156,7 +1156,7 @@ fn test_install_backup_numbered_if_existing_backup_existing() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(file_b_backup));
assert!(at.file_exists(&format!("{file_b}.~2~")));
assert!(at.file_exists(format!("{file_b}.~2~")));
}
#[test]
@ -1182,7 +1182,7 @@ fn test_install_backup_numbered_if_existing_backup_nil() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(file_b_backup));
assert!(at.file_exists(&format!("{file_b}.~2~")));
assert!(at.file_exists(format!("{file_b}.~2~")));
}
#[test]
@ -1205,7 +1205,7 @@ fn test_install_backup_simple() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -1228,7 +1228,7 @@ fn test_install_backup_never() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -1251,7 +1251,7 @@ fn test_install_backup_none() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(!at.file_exists(&format!("{file_b}~")));
assert!(!at.file_exists(format!("{file_b}~")));
}
#[test]
@ -1274,7 +1274,7 @@ fn test_install_backup_off() {
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(!at.file_exists(&format!("{file_b}~")));
assert!(!at.file_exists(format!("{file_b}~")));
}
#[test]

View file

@ -403,7 +403,7 @@ fn test_symlink_implicit_target_dir() {
let file = &path.to_string_lossy();
at.mkdir(dir);
at.touch(file);
at.touch(&path);
ucmd.args(&["-s", file]).succeeds().no_stderr();

View file

@ -609,10 +609,10 @@ fn test_ls_a() {
fn test_ls_width() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-width-1"));
at.touch(&at.plus_as_string("test-width-2"));
at.touch(&at.plus_as_string("test-width-3"));
at.touch(&at.plus_as_string("test-width-4"));
at.touch(at.plus_as_string("test-width-1"));
at.touch(at.plus_as_string("test-width-2"));
at.touch(at.plus_as_string("test-width-3"));
at.touch(at.plus_as_string("test-width-4"));
for option in [
"-w 100",
@ -692,10 +692,10 @@ fn test_ls_width() {
fn test_ls_columns() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-columns-1"));
at.touch(&at.plus_as_string("test-columns-2"));
at.touch(&at.plus_as_string("test-columns-3"));
at.touch(&at.plus_as_string("test-columns-4"));
at.touch(at.plus_as_string("test-columns-1"));
at.touch(at.plus_as_string("test-columns-2"));
at.touch(at.plus_as_string("test-columns-3"));
at.touch(at.plus_as_string("test-columns-4"));
// Columns is the default
let result = scene.ucmd().succeeds();
@ -753,10 +753,10 @@ fn test_ls_columns() {
fn test_ls_across() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-across-1"));
at.touch(&at.plus_as_string("test-across-2"));
at.touch(&at.plus_as_string("test-across-3"));
at.touch(&at.plus_as_string("test-across-4"));
at.touch(at.plus_as_string("test-across-1"));
at.touch(at.plus_as_string("test-across-2"));
at.touch(at.plus_as_string("test-across-3"));
at.touch(at.plus_as_string("test-across-4"));
for option in ACROSS_ARGS {
let result = scene.ucmd().arg(option).succeeds();
@ -781,10 +781,10 @@ fn test_ls_across() {
fn test_ls_commas() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-commas-1"));
at.touch(&at.plus_as_string("test-commas-2"));
at.touch(&at.plus_as_string("test-commas-3"));
at.touch(&at.plus_as_string("test-commas-4"));
at.touch(at.plus_as_string("test-commas-1"));
at.touch(at.plus_as_string("test-commas-2"));
at.touch(at.plus_as_string("test-commas-3"));
at.touch(at.plus_as_string("test-commas-4"));
for option in COMMA_ARGS {
let result = scene.ucmd().arg(option).succeeds();
@ -814,8 +814,8 @@ fn test_ls_zero() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("0-test-zero");
at.touch(&at.plus_as_string("2-test-zero"));
at.touch(&at.plus_as_string("3-test-zero"));
at.touch(at.plus_as_string("2-test-zero"));
at.touch(at.plus_as_string("3-test-zero"));
let ignored_opts = [
"--quoting-style=c",
@ -870,7 +870,7 @@ fn test_ls_zero() {
#[cfg(unix)]
{
at.touch(&at.plus_as_string("1\ntest-zero"));
at.touch(at.plus_as_string("1\ntest-zero"));
let ignored_opts = [
"--quoting-style=c",
@ -933,9 +933,9 @@ fn test_ls_zero() {
fn test_ls_commas_trailing() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-commas-trailing-2"));
at.touch(at.plus_as_string("test-commas-trailing-2"));
at.touch(&at.plus_as_string("test-commas-trailing-1"));
at.touch(at.plus_as_string("test-commas-trailing-1"));
at.append(
"test-commas-trailing-1",
&(0..2000)
@ -957,7 +957,7 @@ fn test_ls_commas_trailing() {
fn test_ls_long() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-long"));
at.touch(at.plus_as_string("test-long"));
for arg in LONG_ARGS {
let result = scene.ucmd().arg(arg).arg("test-long").succeeds();
@ -975,7 +975,7 @@ fn test_ls_long_format() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir(&at.plus_as_string("test-long-dir"));
at.touch(&at.plus_as_string("test-long-dir/test-long-file"));
at.touch(at.plus_as_string("test-long-dir/test-long-file"));
at.mkdir(&at.plus_as_string("test-long-dir/test-long-dir"));
for arg in LONG_ARGS {
@ -1231,9 +1231,9 @@ fn test_ls_long_symlink_color() {
fn test_ls_long_total_size() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-long"));
at.touch(at.plus_as_string("test-long"));
at.append("test-long", "1");
at.touch(&at.plus_as_string("test-long2"));
at.touch(at.plus_as_string("test-long2"));
at.append("test-long2", "2");
let expected_prints: HashMap<_, _> = if cfg!(unix) {
@ -1275,7 +1275,7 @@ fn test_ls_long_total_size() {
fn test_ls_long_formats() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-long-formats"));
at.touch(at.plus_as_string("test-long-formats"));
// Zero or one "." for indicating a file with security context
@ -1422,8 +1422,8 @@ fn test_ls_long_formats() {
fn test_ls_oneline() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-oneline-1"));
at.touch(&at.plus_as_string("test-oneline-2"));
at.touch(at.plus_as_string("test-oneline-1"));
at.touch(at.plus_as_string("test-oneline-2"));
// Bit of a weird situation: in the tests oneline and columns have the same output,
// except on Windows.
@ -1443,7 +1443,7 @@ fn test_ls_deref() {
let path_regexp = r"(.*)test-long.link -> (.*)test-long(.*)";
let re = Regex::new(path_regexp).unwrap();
at.touch(&at.plus_as_string("test-long"));
at.touch(at.plus_as_string("test-long"));
at.symlink_file("test-long", "test-long.link");
assert!(at.is_symlink("test-long.link"));
@ -1808,8 +1808,8 @@ fn test_ls_files_dirs() {
at.mkdir("a/b");
at.mkdir("a/b/c");
at.mkdir("z");
at.touch(&at.plus_as_string("a/a"));
at.touch(&at.plus_as_string("a/b/b"));
at.touch(at.plus_as_string("a/a"));
at.touch(at.plus_as_string("a/b/b"));
scene.ucmd().arg("a").succeeds();
scene.ucmd().arg("a/a").succeeds();
@ -1840,8 +1840,8 @@ fn test_ls_recursive() {
at.mkdir("a/b");
at.mkdir("a/b/c");
at.mkdir("z");
at.touch(&at.plus_as_string("a/a"));
at.touch(&at.plus_as_string("a/b/b"));
at.touch(at.plus_as_string("a/a"));
at.touch(at.plus_as_string("a/b/b"));
scene.ucmd().arg("a").succeeds();
scene.ucmd().arg("a/a").succeeds();
@ -1880,7 +1880,7 @@ fn test_ls_color() {
.join("nested_file")
.to_string_lossy()
.to_string();
at.touch(&nested_file);
at.touch(nested_file);
at.touch("test-color");
let a_with_colors = "\x1b[1;34ma\x1b[0m";
@ -1985,7 +1985,7 @@ fn test_ls_indicator_style() {
at.mkdir("directory");
assert!(at.dir_exists("directory"));
at.touch(&at.plus_as_string("link-src"));
at.touch(at.plus_as_string("link-src"));
at.symlink_file("link-src", "link-dest.link");
assert!(at.is_symlink("link-dest.link"));
@ -2077,7 +2077,7 @@ fn test_ls_indicator_style() {
at.mkdir("directory");
assert!(at.dir_exists("directory"));
at.touch(&at.plus_as_string("link-src"));
at.touch(at.plus_as_string("link-src"));
at.symlink_file("link-src", "link-dest.link");
assert!(at.is_symlink("link-dest.link"));

View file

@ -55,7 +55,7 @@ fn test_mv_move_file_into_dir() {
ucmd.arg(file).arg(dir).succeeds().no_stderr();
assert!(at.file_exists(&format!("{dir}/{file}")));
assert!(at.file_exists(format!("{dir}/{file}")));
}
#[test]
@ -67,17 +67,17 @@ fn test_mv_move_file_between_dirs() {
at.mkdir(dir1);
at.mkdir(dir2);
at.touch(&format!("{dir1}/{file}"));
at.touch(format!("{dir1}/{file}"));
assert!(at.file_exists(&format!("{dir1}/{file}")));
assert!(at.file_exists(format!("{dir1}/{file}")));
ucmd.arg(&format!("{dir1}/{file}"))
.arg(dir2)
.succeeds()
.no_stderr();
assert!(!at.file_exists(&format!("{dir1}/{file}")));
assert!(at.file_exists(&format!("{dir2}/{file}")));
assert!(!at.file_exists(format!("{dir1}/{file}")));
assert!(at.file_exists(format!("{dir2}/{file}")));
}
#[test]
@ -94,7 +94,7 @@ fn test_mv_strip_slashes() {
scene.ucmd().arg(&source).arg(dir).fails();
assert!(!at.file_exists(&format!("{dir}/{file}")));
assert!(!at.file_exists(format!("{dir}/{file}")));
scene
.ucmd()
@ -104,7 +104,7 @@ fn test_mv_strip_slashes() {
.succeeds()
.no_stderr();
assert!(at.file_exists(&format!("{dir}/{file}")));
assert!(at.file_exists(format!("{dir}/{file}")));
}
#[test]
@ -124,8 +124,8 @@ fn test_mv_multiple_files() {
.succeeds()
.no_stderr();
assert!(at.file_exists(&format!("{target_dir}/{file_a}")));
assert!(at.file_exists(&format!("{target_dir}/{file_b}")));
assert!(at.file_exists(format!("{target_dir}/{file_a}")));
assert!(at.file_exists(format!("{target_dir}/{file_b}")));
}
#[test]
@ -305,7 +305,7 @@ fn test_mv_simple_backup() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -324,7 +324,7 @@ fn test_mv_simple_backup_with_file_extension() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -339,7 +339,7 @@ fn test_mv_arg_backup_arg_first() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -360,7 +360,7 @@ fn test_mv_custom_backup_suffix() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}{suffix}")));
assert!(at.file_exists(format!("{file_b}{suffix}")));
}
#[test]
@ -381,7 +381,7 @@ fn test_mv_custom_backup_suffix_hyphen_value() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}{suffix}")));
assert!(at.file_exists(format!("{file_b}{suffix}")));
}
#[test]
@ -401,7 +401,7 @@ fn test_mv_custom_backup_suffix_via_env() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}{suffix}")));
assert!(at.file_exists(format!("{file_b}{suffix}")));
}
#[test]
@ -420,7 +420,7 @@ fn test_mv_backup_numbered_with_t() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}.~1~")));
assert!(at.file_exists(format!("{file_b}.~1~")));
}
#[test]
@ -439,7 +439,7 @@ fn test_mv_backup_numbered() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}.~1~")));
assert!(at.file_exists(format!("{file_b}.~1~")));
}
#[test]
@ -458,7 +458,7 @@ fn test_mv_backup_existing() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -477,7 +477,7 @@ fn test_mv_backup_nil() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -498,7 +498,7 @@ fn test_mv_numbered_if_existing_backup_existing() {
assert!(at.file_exists(file_b));
assert!(at.file_exists(file_b_backup));
assert!(at.file_exists(&format!("{file_b}.~2~")));
assert!(at.file_exists(format!("{file_b}.~2~")));
}
#[test]
@ -519,7 +519,7 @@ fn test_mv_numbered_if_existing_backup_nil() {
assert!(at.file_exists(file_b));
assert!(at.file_exists(file_b_backup));
assert!(at.file_exists(&format!("{file_b}.~2~")));
assert!(at.file_exists(format!("{file_b}.~2~")));
}
#[test]
@ -538,7 +538,7 @@ fn test_mv_backup_simple() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -557,7 +557,7 @@ fn test_mv_backup_never() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{file_b}~")));
assert!(at.file_exists(format!("{file_b}~")));
}
#[test]
@ -576,7 +576,7 @@ fn test_mv_backup_none() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(!at.file_exists(&format!("{file_b}~")));
assert!(!at.file_exists(format!("{file_b}~")));
}
#[test]
@ -595,7 +595,7 @@ fn test_mv_backup_off() {
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(!at.file_exists(&format!("{file_b}~")));
assert!(!at.file_exists(format!("{file_b}~")));
}
#[test]
@ -660,8 +660,8 @@ fn test_mv_target_dir() {
assert!(!at.file_exists(file_a));
assert!(!at.file_exists(file_b));
assert!(at.file_exists(&format!("{dir}/{file_a}")));
assert!(at.file_exists(&format!("{dir}/{file_b}")));
assert!(at.file_exists(format!("{dir}/{file_a}")));
assert!(at.file_exists(format!("{dir}/{file_b}")));
}
#[test]
@ -675,7 +675,7 @@ fn test_mv_target_dir_single_source() {
ucmd.arg("-t").arg(dir).arg(file).succeeds().no_stderr();
assert!(!at.file_exists(file));
assert!(at.file_exists(&format!("{dir}/{file}")));
assert!(at.file_exists(format!("{dir}/{file}")));
}
#[test]

View file

@ -192,7 +192,7 @@ fn test_realpath_existing() {
ucmd.arg("-e")
.arg(".")
.succeeds()
.stdout_only(at.plus_as_string(&format!("{}\n", at.root_dir_resolved())));
.stdout_only(at.plus_as_string(format!("{}\n", at.root_dir_resolved())));
}
#[test]

View file

@ -750,14 +750,14 @@ impl AtPath {
self.subdir.to_str().unwrap().to_owned()
}
pub fn plus(&self, name: &str) -> PathBuf {
pub fn plus<P: AsRef<Path>>(&self, name: P) -> PathBuf {
let mut pathbuf = self.subdir.clone();
pathbuf.push(name);
pathbuf
}
pub fn plus_as_string(&self, name: &str) -> String {
String::from(self.plus(name).to_str().unwrap())
pub fn plus_as_string<P: AsRef<Path>>(&self, name: P) -> String {
self.plus(name).display().to_string()
}
fn minus(&self, name: &str) -> PathBuf {
@ -880,7 +880,8 @@ impl AtPath {
fs::remove_dir(self.plus(dir)).unwrap();
}
pub fn mkdir(&self, dir: &str) {
pub fn mkdir<P: AsRef<Path>>(&self, dir: P) {
let dir = dir.as_ref();
log_info("mkdir", self.plus_as_string(dir));
fs::create_dir(self.plus(dir)).unwrap();
}
@ -897,7 +898,8 @@ impl AtPath {
}
}
pub fn touch(&self, file: &str) {
pub fn touch<P: AsRef<Path>>(&self, file: P) {
let file = file.as_ref();
log_info("touch", self.plus_as_string(file));
File::create(self.plus(file)).unwrap();
}
@ -1020,7 +1022,7 @@ impl AtPath {
}
}
pub fn file_exists(&self, path: &str) -> bool {
pub fn file_exists<P: AsRef<Path>>(&self, path: P) -> bool {
match fs::metadata(self.plus(path)) {
Ok(m) => m.is_file(),
Err(_) => false,