mirror of
https://github.com/uutils/coreutils
synced 2024-07-21 18:04:45 +00:00
Merge branch 'main' into cp-pathbuf
This commit is contained in:
commit
49eb9a2e91
18
.github/workflows/CICD.yml
vendored
18
.github/workflows/CICD.yml
vendored
|
@ -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
.github/workflows/GnuTests.yml
vendored
6
.github/workflows/GnuTests.yml
vendored
|
@ -86,7 +86,7 @@ jobs:
|
|||
run: |
|
||||
## Install dependencies
|
||||
sudo apt-get update
|
||||
sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl
|
||||
sudo apt-get install -y autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl libacl1-dev libattr1-dev libcap-dev
|
||||
- name: Add various locales
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -316,8 +316,8 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
## Install dependencies
|
||||
sudo apt update
|
||||
sudo apt install autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl -y
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl libacl1-dev libattr1-dev libcap-dev
|
||||
- name: Add various locales
|
||||
run: |
|
||||
## Add various locales
|
||||
|
|
6
.markdownlint.yaml
Normal file
6
.markdownlint.yaml
Normal 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
|
||||
|
|
@ -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>.
|
||||
|
|
|
@ -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
|
||||
|
|
218
Cargo.lock
generated
218
Cargo.lock
generated
|
@ -67,7 +67,7 @@ version = "0.2.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.1.19",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
@ -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"
|
||||
|
@ -293,7 +303,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"libc",
|
||||
"unicode-width",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -321,14 +331,15 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
|||
name = "coreutils"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"clap_mangen",
|
||||
"conv",
|
||||
"filetime",
|
||||
"glob",
|
||||
"hex-literal",
|
||||
"is-terminal",
|
||||
"libc",
|
||||
"nix",
|
||||
"once_cell",
|
||||
|
@ -635,7 +646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -834,7 +845,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1045,6 +1056,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
|
@ -1139,6 +1156,28 @@ version = "0.7.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"io-lifetimes 1.0.5",
|
||||
"rustix 0.36.8",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
|
@ -1235,6 +1274,12 @@ version = "0.0.46"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
|
@ -1326,7 +1371,7 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1415,7 +1460,7 @@ version = "1.14.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.1.19",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -1436,9 +1481,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "onig"
|
||||
|
@ -1545,7 +1590,7 @@ dependencies = [
|
|||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1693,7 +1738,7 @@ dependencies = [
|
|||
"byteorder",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"rustix",
|
||||
"rustix 0.35.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1833,13 +1878,19 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rlimit"
|
||||
version = "0.8.3"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7278a1ec8bfd4a4e07515c589f5ff7b309a373f987393aef44813d9dcf87aa3"
|
||||
checksum = "f8a29d87a652dc4d43c586328706bb5cdff211f3f39a530f240b53f7221dab8e"
|
||||
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"
|
||||
|
@ -1899,10 +1950,24 @@ checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9"
|
|||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"io-lifetimes 0.7.5",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
"linux-raw-sys 0.0.46",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes 1.0.5",
|
||||
"libc",
|
||||
"linux-raw-sys 0.1.4",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1934,9 +1999,9 @@ checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
|
|||
|
||||
[[package]]
|
||||
name = "selinux"
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "966a861c0b329c3078d82b404f7086009487123fd0cc905a9caac55d8b13bee1"
|
||||
checksum = "a00576725d21b588213fbd4af84cd7e4cc4304e8e9bd6c0f5a1498a3e2ca6a51"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
|
@ -2053,6 +2118,15 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sm3"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f943a7c5e3089f2bd046221d1e9f4fa59396bf0fe966360983649683086215da"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
|
@ -2161,8 +2235,8 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
"rustix 0.35.13",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2334,8 +2408,8 @@ dependencies = [
|
|||
name = "uu_cat"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"is-terminal",
|
||||
"nix",
|
||||
"thiserror",
|
||||
"uucore",
|
||||
|
@ -2391,6 +2465,7 @@ name = "uu_cksum"
|
|||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"hex",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
|
@ -2432,9 +2507,9 @@ dependencies = [
|
|||
name = "uu_cut"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bstr",
|
||||
"clap",
|
||||
"is-terminal",
|
||||
"memchr",
|
||||
"uucore",
|
||||
]
|
||||
|
@ -2447,7 +2522,7 @@ dependencies = [
|
|||
"clap",
|
||||
"libc",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2503,7 +2578,7 @@ dependencies = [
|
|||
"clap",
|
||||
"glob",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2595,17 +2670,10 @@ dependencies = [
|
|||
name = "uu_hashsum"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
"blake3",
|
||||
"clap",
|
||||
"digest",
|
||||
"hex",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"regex",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"sha3",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
|
@ -2634,7 +2702,7 @@ dependencies = [
|
|||
"clap",
|
||||
"hostname",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2705,10 +2773,10 @@ dependencies = [
|
|||
name = "uu_ls"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"chrono",
|
||||
"clap",
|
||||
"glob",
|
||||
"is-terminal",
|
||||
"lscolors",
|
||||
"number_prefix",
|
||||
"once_cell",
|
||||
|
@ -2759,9 +2827,9 @@ dependencies = [
|
|||
name = "uu_more"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"is-terminal",
|
||||
"nix",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
|
@ -2801,8 +2869,8 @@ dependencies = [
|
|||
name = "uu_nohup"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"is-terminal",
|
||||
"libc",
|
||||
"uucore",
|
||||
]
|
||||
|
@ -2936,7 +3004,7 @@ dependencies = [
|
|||
"libc",
|
||||
"uucore",
|
||||
"walkdir",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3079,7 +3147,7 @@ dependencies = [
|
|||
"libc",
|
||||
"nix",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3097,16 +3165,16 @@ dependencies = [
|
|||
name = "uu_tail"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"fundu",
|
||||
"is-terminal",
|
||||
"libc",
|
||||
"memchr",
|
||||
"notify",
|
||||
"same-file",
|
||||
"uucore",
|
||||
"winapi-util",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3146,7 +3214,7 @@ dependencies = [
|
|||
"filetime",
|
||||
"time",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3186,8 +3254,8 @@ dependencies = [
|
|||
name = "uu_tty"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"is-terminal",
|
||||
"nix",
|
||||
"uucore",
|
||||
]
|
||||
|
@ -3282,7 +3350,7 @@ dependencies = [
|
|||
"clap",
|
||||
"libc",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3299,24 +3367,34 @@ dependencies = [
|
|||
name = "uucore"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
"blake3",
|
||||
"clap",
|
||||
"data-encoding",
|
||||
"data-encoding-macro",
|
||||
"digest",
|
||||
"dns-lookup",
|
||||
"dunce",
|
||||
"glob",
|
||||
"hex",
|
||||
"itertools",
|
||||
"libc",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"nix",
|
||||
"once_cell",
|
||||
"os_display",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"sha3",
|
||||
"sm3",
|
||||
"thiserror",
|
||||
"time",
|
||||
"uucore_procs",
|
||||
"walkdir",
|
||||
"wild",
|
||||
"winapi-util",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
"z85",
|
||||
]
|
||||
|
||||
|
@ -3478,46 +3556,70 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.0"
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
|
|
22
Cargo.toml
22
Cargo.toml
|
@ -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"
|
||||
|
@ -263,7 +263,6 @@ feat_os_windows_legacy = [
|
|||
test = [ "uu_test" ]
|
||||
|
||||
[workspace.dependencies]
|
||||
atty = "0.2"
|
||||
bigdecimal = "0.3"
|
||||
binary-heap-plus = "0.5.0"
|
||||
bstr = "1.0"
|
||||
|
@ -272,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"
|
||||
|
@ -287,6 +287,7 @@ gcd = "2.2"
|
|||
glob = "0.3.0"
|
||||
half = "2.1"
|
||||
indicatif = "0.17"
|
||||
is-terminal = "0.4.3"
|
||||
itertools = "0.10.0"
|
||||
libc = "0.2.139"
|
||||
lscolors = { version = "0.13.0", default-features=false, features = ["nu-ansi-term"] }
|
||||
|
@ -312,7 +313,7 @@ redox_syscall = "0.2"
|
|||
regex = "1.7.1"
|
||||
rust-ini = "0.18.0"
|
||||
same-file = "1.0.6"
|
||||
selinux = "0.3"
|
||||
selinux = "0.4"
|
||||
signal-hook = "0.3.14"
|
||||
smallvec = { version = "1.10", features = ["union"] }
|
||||
strum = "0.24.1"
|
||||
|
@ -332,6 +333,16 @@ windows-sys = { version="0.42.0", default-features=false }
|
|||
xattr = "0.2.3"
|
||||
zip = { version = "0.6.3", default_features=false, features=["deflate"] }
|
||||
|
||||
hex = "0.4.3"
|
||||
md-5 = "0.10.5"
|
||||
sha1 = "0.10.1"
|
||||
sha2 = "0.10.2"
|
||||
sha3 = "0.10.6"
|
||||
blake2b_simd = "1.0.1"
|
||||
blake3 = "1.3.2"
|
||||
sm3 = "0.4.1"
|
||||
digest = "0.10.6"
|
||||
|
||||
uucore = { version=">=0.0.17", package="uucore", path="src/uucore" }
|
||||
uucore_procs = { version=">=0.0.17", package="uucore_procs", path="src/uucore_procs" }
|
||||
uu_ls = { version=">=0.0.17", path="src/uu/ls" }
|
||||
|
@ -342,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 }
|
||||
|
@ -476,13 +488,13 @@ time = { workspace=true, features=["local-offset"] }
|
|||
unindent = "0.1"
|
||||
uucore = { workspace=true, features=["entries", "process", "signals"] }
|
||||
walkdir = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace=true }
|
||||
hex-literal = "0.3.1"
|
||||
rstest = "0.16.0"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
||||
procfs = { version = "0.14", default-features = false }
|
||||
rlimit = "0.8.3"
|
||||
rlimit = "0.9.1"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
nix = { workspace=true, features=["process", "signal", "user"] }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
210
README.md
210
README.md
|
@ -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,80 @@ Building using `make` is a simple process as well.
|
|||
|
||||
To simply build all available utilities:
|
||||
|
||||
```bash
|
||||
$ make
|
||||
```shell
|
||||
make
|
||||
```
|
||||
|
||||
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 +205,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 +347,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 +373,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 +405,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 +412,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 |
|
||||
|
|
14
deny.toml
14
deny.toml
|
@ -58,12 +58,24 @@ highlight = "all"
|
|||
# For each duplicate dependency, indicate the name of the dependency which
|
||||
# introduces it.
|
||||
# spell-checker: disable
|
||||
skip = []
|
||||
skip = [
|
||||
# is-terminal
|
||||
{ name = "hermit-abi", version = "0.3.1" },
|
||||
# is-terminal
|
||||
{ name = "rustix", version = "0.36.8" },
|
||||
# is-terminal (via rustix)
|
||||
{ name = "io-lifetimes", version = "1.0.5" },
|
||||
# is-terminal
|
||||
{ name = "linux-raw-sys", version = "0.1.4" },
|
||||
# is-terminal
|
||||
{ name = "windows-sys", version = "0.45.0" },
|
||||
]
|
||||
# spell-checker: enable
|
||||
|
||||
# This section is considered when running `cargo deny check sources`.
|
||||
# More documentation about the 'sources' section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
||||
|
||||
[sources]
|
||||
unknown-registry = "warn"
|
||||
unknown-git = "warn"
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# Build from source
|
||||
|
||||
{{#include ../../README.md:build }}
|
||||
{{#include ../../README.md:build }}
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
{{ #include ../../CONTRIBUTING.md }}
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
{{ #include ../../CONTRIBUTING.md }}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
{{#include logo.svg}}
|
||||
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
|
||||
<style>
|
||||
/* Make the logo a bit bigger and center */
|
||||
#logo {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!-- spell-checker:ignore pacman pamac nixpkgs -->
|
||||
<!-- spell-checker:ignore pacman pamac nixpkgs openmandriva -->
|
||||
|
||||
# Installation
|
||||
|
||||
|
@ -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
|
||||
|
@ -55,35 +57,45 @@ export PATH=/usr/lib/cargo/bin/coreutils:$PATH
|
|||
|
||||
### Gentoo
|
||||
|
||||
[![Gentoo package](https://repology.org/badge/version-for-repo/gentoo/uutils-coreutils.svg)](https://packages.gentoo.org/packages/sys-apps/uutils)
|
||||
[![Gentoo package](https://repology.org/badge/version-for-repo/gentoo/uutils-coreutils.svg)](https://packages.gentoo.org/packages/sys-apps/uutils-coreutils)
|
||||
|
||||
```bash
|
||||
emerge -pv sys-apps/uutils
|
||||
```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)
|
||||
|
||||
```shell
|
||||
dnf install uutils-coreutils
|
||||
```
|
||||
|
||||
### Ubuntu
|
||||
|
||||
[![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
|
||||
|
@ -94,25 +106,36 @@ 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/)
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
|
@ -122,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.
|
||||
|
|
|
@ -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
|
||||
```
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
11
src/uu/arch/arch.md
Normal file
11
src/uu/arch/arch.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# arch
|
||||
|
||||
```
|
||||
arch
|
||||
```
|
||||
|
||||
Display machine architecture
|
||||
|
||||
## After Help
|
||||
|
||||
Determine architecture name for current machine.
|
|
@ -10,9 +10,10 @@ use platform_info::*;
|
|||
|
||||
use clap::{crate_version, Command};
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::{help_about, help_section};
|
||||
|
||||
static ABOUT: &str = "Display machine architecture";
|
||||
static SUMMARY: &str = "Determine architecture name for current machine.";
|
||||
static ABOUT: &str = help_about!("arch.md");
|
||||
static SUMMARY: &str = help_section!("after help", "arch.md");
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
9
src/uu/basename/basename.md
Normal file
9
src/uu/basename/basename.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# basename
|
||||
|
||||
```
|
||||
basename NAME [SUFFIX]
|
||||
basename OPTION... NAME...
|
||||
```
|
||||
|
||||
Print NAME with any leading directory components removed
|
||||
If specified, also remove a trailing SUFFIX
|
|
@ -11,13 +11,11 @@ use clap::{crate_version, Arg, ArgAction, Command};
|
|||
use std::path::{is_separator, PathBuf};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, UUsageError};
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
static ABOUT: &str = r#"Print NAME with any leading directory components removed
|
||||
If specified, also remove a trailing SUFFIX"#;
|
||||
static ABOUT: &str = help_about!("basename.md");
|
||||
|
||||
const USAGE: &str = "{} NAME [SUFFIX]
|
||||
{} OPTION... NAME...";
|
||||
const USAGE: &str = help_usage!("basename.md");
|
||||
|
||||
pub mod options {
|
||||
pub static MULTIPLE: &str = "multiple";
|
||||
|
|
12
src/uu/basenc/basenc.md
Normal file
12
src/uu/basenc/basenc.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# basenc
|
||||
|
||||
```
|
||||
basenc [OPTION]... [FILE]"
|
||||
```
|
||||
|
||||
Encode/decode data and print to standard output
|
||||
With no FILE, or when FILE is -, read standard input.
|
||||
|
||||
When decoding, the input may contain newlines in addition to the bytes of
|
||||
the formal alphabet. Use --ignore-garbage to attempt to recover
|
||||
from any other non-alphabet bytes in the encoded stream.
|
|
@ -19,14 +19,10 @@ use uucore::{
|
|||
use std::io::{stdin, Read};
|
||||
use uucore::error::UClapError;
|
||||
|
||||
static ABOUT: &str = "\
|
||||
Encode/decode data and print to standard output
|
||||
With no FILE, or when FILE is -, read standard input.
|
||||
use uucore::{help_about, help_usage};
|
||||
|
||||
When decoding, the input may contain newlines in addition to the bytes of
|
||||
the formal alphabet. Use --ignore-garbage to attempt to recover
|
||||
from any other non-alphabet bytes in the encoded stream.
|
||||
";
|
||||
const ABOUT: &str = help_about!("basenc.md");
|
||||
const USAGE: &str = help_usage!("basenc.md");
|
||||
|
||||
const ENCODINGS: &[(&str, Format)] = &[
|
||||
("base64", Format::Base64),
|
||||
|
@ -39,8 +35,6 @@ const ENCODINGS: &[(&str, Format)] = &[
|
|||
("z85", Format::Z85),
|
||||
];
|
||||
|
||||
const USAGE: &str = "{} [OPTION]... [FILE]";
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
let mut command = base_common::base_app(ABOUT, USAGE);
|
||||
for encoding in ENCODINGS {
|
||||
|
|
|
@ -17,7 +17,7 @@ path = "src/cat.rs"
|
|||
[dependencies]
|
||||
clap = { workspace=true }
|
||||
thiserror = { workspace = true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace = true }
|
||||
uucore = { workspace=true, features=["fs", "pipes"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
// last synced with: cat (GNU coreutils) 8.13
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use is_terminal::IsTerminal;
|
||||
use std::fs::{metadata, File};
|
||||
use std::io::{self, Read, Write};
|
||||
use thiserror::Error;
|
||||
|
@ -332,7 +333,7 @@ fn cat_path(
|
|||
let stdin = io::stdin();
|
||||
let mut handle = InputHandle {
|
||||
reader: stdin,
|
||||
is_interactive: atty::is(atty::Stream::Stdin),
|
||||
is_interactive: std::io::stdin().is_terminal(),
|
||||
};
|
||||
cat_handle(&mut handle, options, state)
|
||||
}
|
||||
|
|
11
src/uu/chcon/chcon.md
Normal file
11
src/uu/chcon/chcon.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!-- spell-checker:ignore (vars) RFILE -->
|
||||
# chcon
|
||||
|
||||
```
|
||||
chcon [OPTION]... CONTEXT FILE...
|
||||
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.
|
|
@ -1,13 +1,11 @@
|
|||
// spell-checker:ignore (vars) RFILE
|
||||
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use clap::builder::ValueParser;
|
||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||
use uucore::format_usage;
|
||||
use uucore::{display::Quotable, show_error, show_warning};
|
||||
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;
|
||||
|
@ -21,13 +19,8 @@ mod fts;
|
|||
|
||||
use errors::*;
|
||||
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static ABOUT: &str = "Change the SELinux security context of each FILE to CONTEXT. \n\
|
||||
With --reference, change the security context of each FILE to that of RFILE.";
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... CONTEXT FILE... \n \
|
||||
{} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \
|
||||
{} [OPTION]... --reference=RFILE FILE...";
|
||||
const ABOUT: &str = help_about!("chcon.md");
|
||||
const USAGE: &str = help_usage!("chcon.md");
|
||||
|
||||
pub mod options {
|
||||
pub static HELP: &str = "help";
|
||||
|
@ -152,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
10
src/uu/chgrp/chgrp.md
Normal 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.
|
|
@ -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
16
src/uu/chmod/chmod.md
Normal 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]+'.
|
|
@ -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";
|
||||
|
@ -110,6 +105,7 @@ pub fn uu_app() -> Command {
|
|||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.args_override_self(true)
|
||||
.infer_long_args(true)
|
||||
.arg(
|
||||
Arg::new(options::CHANGES)
|
||||
|
@ -190,16 +186,18 @@ impl Chmoder {
|
|||
let file = Path::new(filename);
|
||||
if !file.exists() {
|
||||
if file.is_symlink() {
|
||||
println!(
|
||||
"failed to change mode of {} from 0000 (---------) to 0000 (---------)",
|
||||
filename.quote()
|
||||
);
|
||||
if !self.quiet {
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
format!("cannot operate on dangling symlink {}", filename.quote()),
|
||||
));
|
||||
}
|
||||
if self.verbose {
|
||||
println!(
|
||||
"failed to change mode of {} from 0000 (---------) to 1500 (r-x-----T)",
|
||||
filename.quote()
|
||||
);
|
||||
}
|
||||
} else if !self.quiet {
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
|
|
9
src/uu/chown/chown.md
Normal file
9
src/uu/chown/chown.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
<!-- spell-checker:ignore RFILE -->
|
||||
# chown
|
||||
|
||||
```
|
||||
chown [OPTION]... [OWNER][:[GROUP]] FILE...
|
||||
chown [OPTION]... --reference=RFILE FILE...
|
||||
```
|
||||
|
||||
Change file owner and group
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
use uucore::display::Quotable;
|
||||
pub use uucore::entries::{self, Group, Locate, Passwd};
|
||||
use uucore::format_usage;
|
||||
use uucore::perms::{chown_base, options, IfFrom};
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
|
||||
|
@ -19,11 +19,9 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
|||
use std::fs;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
static ABOUT: &str = "Change file owner and group";
|
||||
static ABOUT: &str = help_about!("chown.md");
|
||||
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... [OWNER][:[GROUP]] FILE...
|
||||
{} [OPTION]... --reference=RFILE FILE...";
|
||||
const USAGE: &str = help_usage!("chown.md");
|
||||
|
||||
fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option<u32>, Option<u32>, IfFrom)> {
|
||||
let filter = if let Some(spec) = matches.get_one::<String>(options::FROM) {
|
||||
|
|
|
@ -16,7 +16,8 @@ path = "src/cksum.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { workspace=true }
|
||||
uucore = { workspace=true }
|
||||
uucore = { workspace=true, features=["sum"] }
|
||||
hex = { workspace=true }
|
||||
|
||||
[[bin]]
|
||||
name = "cksum"
|
||||
|
|
|
@ -5,134 +5,244 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) fname
|
||||
// spell-checker:ignore (ToDO) fname, algo
|
||||
use clap::{crate_version, Arg, Command};
|
||||
use hex::encode;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::io::{self, stdin, BufReader, Read};
|
||||
use std::iter;
|
||||
use std::path::Path;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::{format_usage, show};
|
||||
|
||||
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
|
||||
const CRC_TABLE_LEN: usize = 256;
|
||||
const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table();
|
||||
use uucore::{
|
||||
error::{FromIo, UResult},
|
||||
format_usage,
|
||||
sum::{
|
||||
div_ceil, Blake2b, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha512, Sm3,
|
||||
BSD, CRC, SYSV,
|
||||
},
|
||||
};
|
||||
|
||||
const USAGE: &str = "{} [OPTIONS] [FILE]...";
|
||||
const ABOUT: &str = "Print CRC and size for each file";
|
||||
|
||||
const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] {
|
||||
let mut table = [0; CRC_TABLE_LEN];
|
||||
const ALGORITHM_OPTIONS_SYSV: &str = "sysv";
|
||||
const ALGORITHM_OPTIONS_BSD: &str = "bsd";
|
||||
const ALGORITHM_OPTIONS_CRC: &str = "crc";
|
||||
const ALGORITHM_OPTIONS_MD5: &str = "md5";
|
||||
const ALGORITHM_OPTIONS_SHA1: &str = "sha1";
|
||||
const ALGORITHM_OPTIONS_SHA224: &str = "sha224";
|
||||
const ALGORITHM_OPTIONS_SHA256: &str = "sha256";
|
||||
const ALGORITHM_OPTIONS_SHA384: &str = "sha384";
|
||||
const ALGORITHM_OPTIONS_SHA512: &str = "sha512";
|
||||
const ALGORITHM_OPTIONS_BLAKE2B: &str = "blake2b";
|
||||
const ALGORITHM_OPTIONS_SM3: &str = "sm3";
|
||||
|
||||
let mut i = 0;
|
||||
while i < CRC_TABLE_LEN {
|
||||
table[i] = crc_entry(i as u8);
|
||||
fn detect_algo(program: &str) -> (&'static str, Box<dyn Digest + 'static>, usize) {
|
||||
match program {
|
||||
ALGORITHM_OPTIONS_SYSV => (
|
||||
ALGORITHM_OPTIONS_SYSV,
|
||||
Box::new(SYSV::new()) as Box<dyn Digest>,
|
||||
512,
|
||||
),
|
||||
ALGORITHM_OPTIONS_BSD => (
|
||||
ALGORITHM_OPTIONS_BSD,
|
||||
Box::new(BSD::new()) as Box<dyn Digest>,
|
||||
1024,
|
||||
),
|
||||
ALGORITHM_OPTIONS_CRC => (
|
||||
ALGORITHM_OPTIONS_CRC,
|
||||
Box::new(CRC::new()) as Box<dyn Digest>,
|
||||
256,
|
||||
),
|
||||
ALGORITHM_OPTIONS_MD5 => (
|
||||
ALGORITHM_OPTIONS_MD5,
|
||||
Box::new(Md5::new()) as Box<dyn Digest>,
|
||||
128,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SHA1 => (
|
||||
ALGORITHM_OPTIONS_SHA1,
|
||||
Box::new(Sha1::new()) as Box<dyn Digest>,
|
||||
160,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SHA224 => (
|
||||
ALGORITHM_OPTIONS_SHA224,
|
||||
Box::new(Sha224::new()) as Box<dyn Digest>,
|
||||
224,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SHA256 => (
|
||||
ALGORITHM_OPTIONS_SHA256,
|
||||
Box::new(Sha256::new()) as Box<dyn Digest>,
|
||||
256,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SHA384 => (
|
||||
ALGORITHM_OPTIONS_SHA384,
|
||||
Box::new(Sha384::new()) as Box<dyn Digest>,
|
||||
384,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SHA512 => (
|
||||
ALGORITHM_OPTIONS_SHA512,
|
||||
Box::new(Sha512::new()) as Box<dyn Digest>,
|
||||
512,
|
||||
),
|
||||
ALGORITHM_OPTIONS_BLAKE2B => (
|
||||
ALGORITHM_OPTIONS_BLAKE2B,
|
||||
Box::new(Blake2b::new()) as Box<dyn Digest>,
|
||||
512,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SM3 => (
|
||||
ALGORITHM_OPTIONS_SM3,
|
||||
Box::new(Sm3::new()) as Box<dyn Digest>,
|
||||
512,
|
||||
),
|
||||
_ => unreachable!("unknown algorithm: clap should have prevented this case"),
|
||||
}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
struct Options {
|
||||
algo_name: &'static str,
|
||||
digest: Box<dyn Digest + 'static>,
|
||||
output_bits: usize,
|
||||
}
|
||||
|
||||
/// Calculate checksum
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `options` - CLI options for the assigning checksum algorithm
|
||||
/// * `files` - A iterator of OsStr which is a bunch of files that are using for calculating checksum
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn cksum<'a, I>(mut options: Options, files: I) -> UResult<()>
|
||||
where
|
||||
I: Iterator<Item = &'a OsStr>,
|
||||
{
|
||||
for filename in files {
|
||||
let filename = Path::new(filename);
|
||||
let stdin_buf;
|
||||
let file_buf;
|
||||
let not_file = filename == OsStr::new("-");
|
||||
let mut file = BufReader::new(if not_file {
|
||||
stdin_buf = stdin();
|
||||
Box::new(stdin_buf) as Box<dyn Read>
|
||||
} else if filename.is_dir() {
|
||||
Box::new(BufReader::new(io::empty())) as Box<dyn Read>
|
||||
} else {
|
||||
file_buf =
|
||||
File::open(filename).map_err_context(|| filename.to_str().unwrap().to_string())?;
|
||||
Box::new(file_buf) as Box<dyn Read>
|
||||
});
|
||||
let (sum, sz) = digest_read(&mut options.digest, &mut file, options.output_bits)
|
||||
.map_err_context(|| "failed to read input".to_string())?;
|
||||
|
||||
// The BSD checksum output is 5 digit integer
|
||||
let bsd_width = 5;
|
||||
match (options.algo_name, not_file) {
|
||||
(ALGORITHM_OPTIONS_SYSV, true) => println!(
|
||||
"{} {}",
|
||||
sum.parse::<u16>().unwrap(),
|
||||
div_ceil(sz, options.output_bits)
|
||||
),
|
||||
(ALGORITHM_OPTIONS_SYSV, false) => println!(
|
||||
"{} {} {}",
|
||||
sum.parse::<u16>().unwrap(),
|
||||
div_ceil(sz, options.output_bits),
|
||||
filename.display()
|
||||
),
|
||||
(ALGORITHM_OPTIONS_BSD, true) => println!(
|
||||
"{:0bsd_width$} {:bsd_width$}",
|
||||
sum.parse::<u16>().unwrap(),
|
||||
div_ceil(sz, options.output_bits)
|
||||
),
|
||||
(ALGORITHM_OPTIONS_BSD, false) => println!(
|
||||
"{:0bsd_width$} {:bsd_width$} {}",
|
||||
sum.parse::<u16>().unwrap(),
|
||||
div_ceil(sz, options.output_bits),
|
||||
filename.display()
|
||||
),
|
||||
(_, true) => println!("{sum} {sz}"),
|
||||
(_, false) => println!("{sum} {sz} {}", filename.display()),
|
||||
}
|
||||
}
|
||||
|
||||
table
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const fn crc_entry(input: u8) -> u32 {
|
||||
let mut crc = (input as u32) << 24;
|
||||
fn digest_read<T: Read>(
|
||||
digest: &mut Box<dyn Digest>,
|
||||
reader: &mut BufReader<T>,
|
||||
output_bits: usize,
|
||||
) -> io::Result<(String, usize)> {
|
||||
digest.reset();
|
||||
|
||||
let mut i = 0;
|
||||
while i < 8 {
|
||||
let if_condition = crc & 0x8000_0000;
|
||||
let if_body = (crc << 1) ^ 0x04c1_1db7;
|
||||
let else_body = crc << 1;
|
||||
// Read bytes from `reader` and write those bytes to `digest`.
|
||||
//
|
||||
// If `binary` is `false` and the operating system is Windows, then
|
||||
// `DigestWriter` replaces "\r\n" with "\n" before it writes the
|
||||
// bytes into `digest`. Otherwise, it just inserts the bytes as-is.
|
||||
//
|
||||
// In order to support replacing "\r\n", we must call `finalize()`
|
||||
// in order to support the possibility that the last character read
|
||||
// from the reader was "\r". (This character gets buffered by
|
||||
// `DigestWriter` and only written if the following character is
|
||||
// "\n". But when "\r" is the last character read, we need to force
|
||||
// it to be written.)
|
||||
let mut digest_writer = DigestWriter::new(digest, true);
|
||||
let output_size = std::io::copy(reader, &mut digest_writer)? as usize;
|
||||
digest_writer.finalize();
|
||||
|
||||
// NOTE: i feel like this is easier to understand than emulating an if statement in bitwise
|
||||
// ops
|
||||
let condition_table = [else_body, if_body];
|
||||
|
||||
crc = condition_table[(if_condition != 0) as usize];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
crc
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn crc_update(crc: u32, input: u8) -> u32 {
|
||||
(crc << 8) ^ CRC_TABLE[((crc >> 24) as usize ^ input as usize) & 0xFF]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn crc_final(mut crc: u32, mut length: usize) -> u32 {
|
||||
while length != 0 {
|
||||
crc = crc_update(crc, length as u8);
|
||||
length >>= 8;
|
||||
}
|
||||
|
||||
!crc
|
||||
}
|
||||
|
||||
fn init_byte_array() -> Vec<u8> {
|
||||
vec![0; 1024 * 1024]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cksum(fname: &str) -> io::Result<(u32, usize)> {
|
||||
let mut crc = 0u32;
|
||||
let mut size = 0usize;
|
||||
|
||||
let mut rd: Box<dyn Read> = match fname {
|
||||
"-" => Box::new(stdin()),
|
||||
_ => {
|
||||
let p = Path::new(fname);
|
||||
|
||||
// Directories should not give an error, but should be interpreted
|
||||
// as empty files to match GNU semantics.
|
||||
if p.is_dir() {
|
||||
Box::new(BufReader::new(io::empty())) as Box<dyn Read>
|
||||
} else {
|
||||
Box::new(BufReader::new(File::open(p)?)) as Box<dyn Read>
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut bytes = init_byte_array();
|
||||
loop {
|
||||
let num_bytes = rd.read(&mut bytes)?;
|
||||
if num_bytes == 0 {
|
||||
return Ok((crc_final(crc, size), size));
|
||||
}
|
||||
for &b in bytes[..num_bytes].iter() {
|
||||
crc = crc_update(crc, b);
|
||||
}
|
||||
size += num_bytes;
|
||||
if digest.output_bits() > 0 {
|
||||
Ok((digest.result_str(), output_size))
|
||||
} else {
|
||||
// Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016)
|
||||
let mut bytes = Vec::new();
|
||||
bytes.resize((output_bits + 7) / 8, 0);
|
||||
digest.hash_finalize(&mut bytes);
|
||||
Ok((encode(bytes), output_size))
|
||||
}
|
||||
}
|
||||
|
||||
mod options {
|
||||
pub static FILE: &str = "file";
|
||||
pub static ALGORITHM: &str = "algorithm";
|
||||
}
|
||||
|
||||
const ALGORITHM_HELP_DESC: &str =
|
||||
"DIGEST determines the digest algorithm and default output format:\n\
|
||||
\n\
|
||||
-a=sysv: (equivalent to sum -s)\n\
|
||||
-a=bsd: (equivalent to sum -r)\n\
|
||||
-a=crc: (equivalent to cksum)\n\
|
||||
-a=md5: (equivalent to md5sum)\n\
|
||||
-a=sha1: (equivalent to sha1sum)\n\
|
||||
-a=sha224: (equivalent to sha224sum)\n\
|
||||
-a=sha256: (equivalent to sha256sum)\n\
|
||||
-a=sha384: (equivalent to sha384sum)\n\
|
||||
-a=sha512: (equivalent to sha512sum)\n\
|
||||
-a=blake2b: (equivalent to b2sum)\n\
|
||||
-a=sm3: (only available through cksum)\n";
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args.collect_ignore();
|
||||
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
|
||||
let files: Vec<String> = match matches.get_many::<String>(options::FILE) {
|
||||
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
|
||||
None => vec![],
|
||||
let algo_name: &str = match matches.get_one::<String>(options::ALGORITHM) {
|
||||
Some(v) => v,
|
||||
None => ALGORITHM_OPTIONS_CRC,
|
||||
};
|
||||
|
||||
if files.is_empty() {
|
||||
let (crc, size) = cksum("-")?;
|
||||
println!("{crc} {size}");
|
||||
return Ok(());
|
||||
}
|
||||
let (name, algo, bits) = detect_algo(algo_name);
|
||||
let opts = Options {
|
||||
algo_name: name,
|
||||
digest: algo,
|
||||
output_bits: bits,
|
||||
};
|
||||
|
||||
match matches.get_many::<String>(options::FILE) {
|
||||
Some(files) => cksum(opts, files.map(OsStr::new))?,
|
||||
None => cksum(opts, iter::once(OsStr::new("-")))?,
|
||||
};
|
||||
|
||||
for fname in &files {
|
||||
match cksum(fname.as_ref()).map_err_context(|| format!("{}", fname.maybe_quote())) {
|
||||
Ok((crc, size)) => println!("{crc} {size} {fname}"),
|
||||
Err(err) => show!(err),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -148,4 +258,25 @@ pub fn uu_app() -> Command {
|
|||
.action(clap::ArgAction::Append)
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::ALGORITHM)
|
||||
.long(options::ALGORITHM)
|
||||
.short('a')
|
||||
.help("select the digest type to use. See DIGEST below")
|
||||
.value_name("ALGORITHM")
|
||||
.value_parser([
|
||||
ALGORITHM_OPTIONS_SYSV,
|
||||
ALGORITHM_OPTIONS_BSD,
|
||||
ALGORITHM_OPTIONS_CRC,
|
||||
ALGORITHM_OPTIONS_MD5,
|
||||
ALGORITHM_OPTIONS_SHA1,
|
||||
ALGORITHM_OPTIONS_SHA224,
|
||||
ALGORITHM_OPTIONS_SHA256,
|
||||
ALGORITHM_OPTIONS_SHA384,
|
||||
ALGORITHM_OPTIONS_SHA512,
|
||||
ALGORITHM_OPTIONS_BLAKE2B,
|
||||
ALGORITHM_OPTIONS_SM3,
|
||||
]),
|
||||
)
|
||||
.after_help(ALGORITHM_HELP_DESC)
|
||||
}
|
||||
|
|
13
src/uu/comm/comm.md
Normal file
13
src/uu/comm/comm.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# comm
|
||||
|
||||
```
|
||||
comm [OPTION]... FILE1 FILE2
|
||||
```
|
||||
|
||||
Compare two sorted files line by line.
|
||||
|
||||
When FILE1 or FILE2 (not both) is -, read standard input.
|
||||
|
||||
With no options, produce three-column output. Column one contains
|
||||
lines unique to FILE1, column two contains lines unique to FILE2,
|
||||
and column three contains lines common to both files.
|
|
@ -8,18 +8,17 @@
|
|||
// spell-checker:ignore (ToDO) delim mkdelim
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{self, stdin, BufRead, BufReader, Stdin};
|
||||
use std::path::Path;
|
||||
use uucore::error::FromIo;
|
||||
use uucore::error::UResult;
|
||||
use uucore::format_usage;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||
|
||||
static ABOUT: &str = "Compare two sorted files line by line";
|
||||
static LONG_HELP: &str = "";
|
||||
const USAGE: &str = "{} [OPTION]... FILE1 FILE2";
|
||||
const ABOUT: &str = help_about!("comm.md");
|
||||
const USAGE: &str = help_usage!("comm.md");
|
||||
|
||||
mod options {
|
||||
pub const COLUMN_1: &str = "1";
|
||||
|
@ -30,6 +29,7 @@ mod options {
|
|||
pub const FILE_1: &str = "FILE1";
|
||||
pub const FILE_2: &str = "FILE2";
|
||||
pub const TOTAL: &str = "total";
|
||||
pub const ZERO_TERMINATED: &str = "zero-terminated";
|
||||
}
|
||||
|
||||
fn column_width(col: &str, opts: &ArgMatches) -> usize {
|
||||
|
@ -40,23 +40,66 @@ fn column_width(col: &str, opts: &ArgMatches) -> usize {
|
|||
}
|
||||
}
|
||||
|
||||
fn ensure_nl(line: &mut String) {
|
||||
if !line.ends_with('\n') {
|
||||
line.push('\n');
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy)]
|
||||
enum LineEnding {
|
||||
Newline = b'\n',
|
||||
Nul = 0,
|
||||
}
|
||||
|
||||
impl From<LineEnding> for u8 {
|
||||
fn from(line_ending: LineEnding) -> Self {
|
||||
line_ending as Self
|
||||
}
|
||||
}
|
||||
|
||||
enum LineReader {
|
||||
impl From<bool> for LineEnding {
|
||||
fn from(is_zero_terminated: bool) -> Self {
|
||||
if is_zero_terminated {
|
||||
Self::Nul
|
||||
} else {
|
||||
Self::Newline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LineEnding {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Newline => writeln!(f),
|
||||
Self::Nul => write!(f, "\0"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Input {
|
||||
Stdin(Stdin),
|
||||
FileIn(BufReader<File>),
|
||||
}
|
||||
|
||||
struct LineReader {
|
||||
line_ending: LineEnding,
|
||||
input: Input,
|
||||
}
|
||||
|
||||
impl LineReader {
|
||||
fn read_line(&mut self, buf: &mut String) -> io::Result<usize> {
|
||||
match *self {
|
||||
Self::Stdin(ref mut r) => r.read_line(buf),
|
||||
Self::FileIn(ref mut r) => r.read_line(buf),
|
||||
fn new(input: Input, line_ending: LineEnding) -> Self {
|
||||
Self { input, line_ending }
|
||||
}
|
||||
|
||||
fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
|
||||
let line_ending = self.line_ending.into();
|
||||
|
||||
let result = match &mut self.input {
|
||||
Input::Stdin(r) => r.lock().read_until(line_ending, buf),
|
||||
Input::FileIn(r) => r.read_until(line_ending, buf),
|
||||
};
|
||||
|
||||
if !buf.ends_with(&[line_ending]) {
|
||||
buf.push(line_ending);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,9 +115,9 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
let delim_col_2 = delim.repeat(width_col_1);
|
||||
let delim_col_3 = delim.repeat(width_col_1 + width_col_2);
|
||||
|
||||
let ra = &mut String::new();
|
||||
let ra = &mut Vec::new();
|
||||
let mut na = a.read_line(ra);
|
||||
let rb = &mut String::new();
|
||||
let rb = &mut Vec::new();
|
||||
let mut nb = b.read_line(rb);
|
||||
|
||||
let mut total_col_1 = 0;
|
||||
|
@ -97,8 +140,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
match ord {
|
||||
Ordering::Less => {
|
||||
if !opts.get_flag(options::COLUMN_1) {
|
||||
ensure_nl(ra);
|
||||
print!("{ra}");
|
||||
print!("{}", String::from_utf8_lossy(ra));
|
||||
}
|
||||
ra.clear();
|
||||
na = a.read_line(ra);
|
||||
|
@ -106,8 +148,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
}
|
||||
Ordering::Greater => {
|
||||
if !opts.get_flag(options::COLUMN_2) {
|
||||
ensure_nl(rb);
|
||||
print!("{delim_col_2}{rb}");
|
||||
print!("{delim_col_2}{}", String::from_utf8_lossy(rb));
|
||||
}
|
||||
rb.clear();
|
||||
nb = b.read_line(rb);
|
||||
|
@ -115,8 +156,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
}
|
||||
Ordering::Equal => {
|
||||
if !opts.get_flag(options::COLUMN_3) {
|
||||
ensure_nl(ra);
|
||||
print!("{delim_col_3}{ra}");
|
||||
print!("{delim_col_3}{}", String::from_utf8_lossy(ra));
|
||||
}
|
||||
ra.clear();
|
||||
rb.clear();
|
||||
|
@ -128,17 +168,20 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
}
|
||||
|
||||
if opts.get_flag(options::TOTAL) {
|
||||
println!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total");
|
||||
let line_ending = LineEnding::from(opts.get_flag(options::ZERO_TERMINATED));
|
||||
print!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total{line_ending}");
|
||||
}
|
||||
}
|
||||
|
||||
fn open_file(name: &str) -> io::Result<LineReader> {
|
||||
match name {
|
||||
"-" => Ok(LineReader::Stdin(stdin())),
|
||||
_ => {
|
||||
let f = File::open(Path::new(name))?;
|
||||
Ok(LineReader::FileIn(BufReader::new(f)))
|
||||
}
|
||||
fn open_file(name: &str, line_ending: LineEnding) -> io::Result<LineReader> {
|
||||
if name == "-" {
|
||||
Ok(LineReader::new(Input::Stdin(stdin()), line_ending))
|
||||
} else {
|
||||
let f = File::open(Path::new(name))?;
|
||||
Ok(LineReader::new(
|
||||
Input::FileIn(BufReader::new(f)),
|
||||
line_ending,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,10 +190,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let args = args.collect_lossy();
|
||||
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
let line_ending = LineEnding::from(matches.get_flag(options::ZERO_TERMINATED));
|
||||
let filename1 = matches.get_one::<String>(options::FILE_1).unwrap();
|
||||
let filename2 = matches.get_one::<String>(options::FILE_2).unwrap();
|
||||
let mut f1 = open_file(filename1).map_err_context(|| filename1.to_string())?;
|
||||
let mut f2 = open_file(filename2).map_err_context(|| filename2.to_string())?;
|
||||
let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?;
|
||||
let mut f2 = open_file(filename2, line_ending).map_err_context(|| filename2.to_string())?;
|
||||
|
||||
comm(&mut f1, &mut f2, &matches);
|
||||
Ok(())
|
||||
|
@ -160,7 +204,6 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(LONG_HELP)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
.arg(
|
||||
|
@ -189,6 +232,14 @@ pub fn uu_app() -> Command {
|
|||
.default_value(options::DELIMITER_DEFAULT)
|
||||
.hide_default_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::ZERO_TERMINATED)
|
||||
.long(options::ZERO_TERMINATED)
|
||||
.short('z')
|
||||
.overrides_with(options::ZERO_TERMINATED)
|
||||
.help("line delimiter is NUL, not newline")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::FILE_1)
|
||||
.required(true)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -93,7 +93,7 @@ impl<'a> Context<'a> {
|
|||
fn new(root: &'a Path, target: &'a Path) -> std::io::Result<Self> {
|
||||
let current_dir = env::current_dir()?;
|
||||
let root_path = current_dir.join(root);
|
||||
let root_parent = if target.exists() {
|
||||
let root_parent = if target.exists() && !root.to_str().unwrap().ends_with("/.") {
|
||||
root_path.parent().map(|p| p.to_path_buf())
|
||||
} else {
|
||||
Some(root_path)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -19,7 +19,7 @@ clap = { workspace=true }
|
|||
uucore = { workspace=true }
|
||||
memchr = { workspace=true }
|
||||
bstr = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace=true }
|
||||
|
||||
[[bin]]
|
||||
name = "cut"
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
use bstr::io::BufReadExt;
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use is_terminal::IsTerminal;
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
|
||||
use std::path::Path;
|
||||
|
@ -136,7 +137,7 @@ enum Mode {
|
|||
}
|
||||
|
||||
fn stdout_writer() -> Box<dyn Write> {
|
||||
if atty::is(atty::Stream::Stdout) {
|
||||
if std::io::stdout().is_terminal() {
|
||||
Box::new(stdout())
|
||||
} else {
|
||||
Box::new(BufWriter::new(stdout())) as Box<dyn Write>
|
||||
|
|
10
src/uu/date/date.md
Normal file
10
src/uu/date/date.md
Normal 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
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
204
src/uu/dd/dd.md
204
src/uu/dd/dd.md
|
@ -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.
|
||||
|
|
|
@ -27,7 +27,9 @@ use std::cmp;
|
|||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Stdout, Write};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Stdin, Stdout, Write};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::Path;
|
||||
|
@ -90,30 +92,106 @@ impl Num {
|
|||
}
|
||||
}
|
||||
|
||||
struct Input<'a, R: Read> {
|
||||
src: R,
|
||||
/// Data sources.
|
||||
enum Source {
|
||||
/// Input from stdin.
|
||||
Stdin(Stdin),
|
||||
|
||||
/// Input from a file.
|
||||
File(File),
|
||||
|
||||
/// Input from a named pipe, also known as a FIFO.
|
||||
#[cfg(unix)]
|
||||
Fifo(File),
|
||||
}
|
||||
|
||||
impl Source {
|
||||
fn skip(&mut self, n: u64) -> io::Result<u64> {
|
||||
match self {
|
||||
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
|
||||
Ok(m) if m < n => {
|
||||
show_error!("'standard input': cannot skip to specified offset");
|
||||
Ok(m)
|
||||
}
|
||||
Ok(m) => Ok(m),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Self::File(f) => f.seek(io::SeekFrom::Start(n)),
|
||||
#[cfg(unix)]
|
||||
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Source {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match self {
|
||||
Self::Stdin(stdin) => stdin.read(buf),
|
||||
Self::File(f) => f.read(buf),
|
||||
#[cfg(unix)]
|
||||
Self::Fifo(f) => f.read(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The source of the data, configured with the given settings.
|
||||
///
|
||||
/// Use the [`Input::new_stdin`] or [`Input::new_file`] functions to
|
||||
/// construct a new instance of this struct. Then pass the instance to
|
||||
/// the [`dd_copy`] function to execute the main copy operation
|
||||
/// for `dd`.
|
||||
struct Input<'a> {
|
||||
/// The source from which bytes will be read.
|
||||
src: Source,
|
||||
|
||||
/// Configuration settings for how to read the data.
|
||||
settings: &'a Settings,
|
||||
}
|
||||
|
||||
impl<'a> Input<'a, io::Stdin> {
|
||||
fn new(settings: &'a Settings) -> UResult<Self> {
|
||||
let mut input = Self {
|
||||
src: io::stdin(),
|
||||
settings,
|
||||
impl<'a> Input<'a> {
|
||||
/// Instantiate this struct with stdin as a source.
|
||||
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
|
||||
let mut src = Source::Stdin(io::stdin());
|
||||
if settings.skip > 0 {
|
||||
src.skip(settings.skip)?;
|
||||
}
|
||||
Ok(Self { src, settings })
|
||||
}
|
||||
|
||||
/// Instantiate this struct with the named file as a source.
|
||||
fn new_file(filename: &Path, settings: &'a Settings) -> UResult<Self> {
|
||||
let src = {
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.read(true);
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
if let Some(libc_flags) = make_linux_iflags(&settings.iflags) {
|
||||
opts.custom_flags(libc_flags);
|
||||
}
|
||||
|
||||
opts.open(filename)
|
||||
.map_err_context(|| format!("failed to open {}", filename.quote()))?
|
||||
};
|
||||
|
||||
let mut src = Source::File(src);
|
||||
if settings.skip > 0 {
|
||||
if let Err(e) = input.read_skip(settings.skip) {
|
||||
if let io::ErrorKind::UnexpectedEof = e.kind() {
|
||||
show_error!("'standard input': cannot skip to specified offset");
|
||||
} else {
|
||||
return io::Result::Err(e)
|
||||
.map_err_context(|| "I/O error while skipping".to_string());
|
||||
}
|
||||
}
|
||||
src.skip(settings.skip)?;
|
||||
}
|
||||
Ok(Self { src, settings })
|
||||
}
|
||||
|
||||
Ok(input)
|
||||
/// Instantiate this struct with the named pipe as a source.
|
||||
#[cfg(unix)]
|
||||
fn new_fifo(filename: &Path, settings: &'a Settings) -> UResult<Self> {
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.read(true);
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
opts.custom_flags(make_linux_iflags(&settings.iflags).unwrap_or(0));
|
||||
let mut src = Source::Fifo(opts.open(filename)?);
|
||||
if settings.skip > 0 {
|
||||
src.skip(settings.skip)?;
|
||||
}
|
||||
Ok(Self { src, settings })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,31 +231,7 @@ fn make_linux_iflags(iflags: &IFlags) -> Option<libc::c_int> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Input<'a, File> {
|
||||
fn new(filename: &Path, settings: &'a Settings) -> UResult<Self> {
|
||||
let mut src = {
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.read(true);
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
if let Some(libc_flags) = make_linux_iflags(&settings.iflags) {
|
||||
opts.custom_flags(libc_flags);
|
||||
}
|
||||
|
||||
opts.open(filename)
|
||||
.map_err_context(|| format!("failed to open {}", filename.quote()))?
|
||||
};
|
||||
|
||||
if settings.skip > 0 {
|
||||
src.seek(io::SeekFrom::Start(settings.skip))
|
||||
.map_err_context(|| "failed to seek in input file".to_string())?;
|
||||
}
|
||||
|
||||
Ok(Self { src, settings })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Read> Read for Input<'a, R> {
|
||||
impl<'a> Read for Input<'a> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let mut base_idx = 0;
|
||||
let target_len = buf.len();
|
||||
|
@ -200,7 +254,7 @@ impl<'a, R: Read> Read for Input<'a, R> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Read> Input<'a, R> {
|
||||
impl<'a> Input<'a> {
|
||||
/// Fills a given buffer.
|
||||
/// Reads in increments of 'self.ibs'.
|
||||
/// The start of each ibs-sized read follows the previous one.
|
||||
|
@ -266,20 +320,6 @@ impl<'a, R: Read> Input<'a, R> {
|
|||
records_truncated: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Skips amount_to_read bytes from the Input by copying into a sink
|
||||
fn read_skip(&mut self, amount_to_read: u64) -> std::io::Result<()> {
|
||||
let copy_result = io::copy(&mut self.src.by_ref().take(amount_to_read), &mut io::sink());
|
||||
if let Ok(n) = copy_result {
|
||||
if n != amount_to_read {
|
||||
io::Result::Err(io::Error::new(io::ErrorKind::UnexpectedEof, ""))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
io::Result::Err(copy_result.unwrap_err())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Density {
|
||||
|
@ -297,6 +337,14 @@ enum Dest {
|
|||
/// The [`Density`] component indicates whether to attempt to
|
||||
/// write a sparse file when all-zero blocks are encountered.
|
||||
File(File, Density),
|
||||
|
||||
/// Output to a named pipe, also known as a FIFO.
|
||||
#[cfg(unix)]
|
||||
Fifo(File),
|
||||
|
||||
/// Output to nothing, dropping each byte written to the output.
|
||||
#[cfg(unix)]
|
||||
Sink,
|
||||
}
|
||||
|
||||
impl Dest {
|
||||
|
@ -307,6 +355,13 @@ impl Dest {
|
|||
f.flush()?;
|
||||
f.sync_all()
|
||||
}
|
||||
#[cfg(unix)]
|
||||
Self::Fifo(f) => {
|
||||
f.flush()?;
|
||||
f.sync_all()
|
||||
}
|
||||
#[cfg(unix)]
|
||||
Self::Sink => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,6 +372,13 @@ impl Dest {
|
|||
f.flush()?;
|
||||
f.sync_data()
|
||||
}
|
||||
#[cfg(unix)]
|
||||
Self::Fifo(f) => {
|
||||
f.flush()?;
|
||||
f.sync_data()
|
||||
}
|
||||
#[cfg(unix)]
|
||||
Self::Sink => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,17 +386,24 @@ impl Dest {
|
|||
match self {
|
||||
Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout),
|
||||
Self::File(f, _) => f.seek(io::SeekFrom::Start(n)),
|
||||
#[cfg(unix)]
|
||||
Self::Fifo(f) => {
|
||||
// Seeking in a named pipe means *reading* from the pipe.
|
||||
io::copy(&mut f.take(n), &mut io::sink())
|
||||
}
|
||||
#[cfg(unix)]
|
||||
Self::Sink => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Truncate the underlying file to the current stream position, if possible.
|
||||
fn truncate(&mut self) -> io::Result<()> {
|
||||
match self {
|
||||
Self::Stdout(_) => Ok(()),
|
||||
Self::File(f, _) => {
|
||||
let pos = f.stream_position()?;
|
||||
f.set_len(pos)
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -357,6 +426,10 @@ impl Write for Dest {
|
|||
}
|
||||
Self::File(f, _) => f.write(buf),
|
||||
Self::Stdout(stdout) => stdout.write(buf),
|
||||
#[cfg(unix)]
|
||||
Self::Fifo(f) => f.write(buf),
|
||||
#[cfg(unix)]
|
||||
Self::Sink => Ok(buf.len()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,6 +437,10 @@ impl Write for Dest {
|
|||
match self {
|
||||
Self::Stdout(stdout) => stdout.flush(),
|
||||
Self::File(f, _) => f.flush(),
|
||||
#[cfg(unix)]
|
||||
Self::Fifo(f) => f.flush(),
|
||||
#[cfg(unix)]
|
||||
Self::Sink => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -372,7 +449,7 @@ impl Write for Dest {
|
|||
///
|
||||
/// Use the [`Output::new_stdout`] or [`Output::new_file`] functions
|
||||
/// to construct a new instance of this struct. Then use the
|
||||
/// [`Output::dd_out`] function to execute the main copy operation for
|
||||
/// [`dd_copy`] function to execute the main copy operation for
|
||||
/// `dd`.
|
||||
struct Output<'a> {
|
||||
/// The destination to which bytes will be written.
|
||||
|
@ -433,6 +510,35 @@ impl<'a> Output<'a> {
|
|||
Ok(Self { dst, settings })
|
||||
}
|
||||
|
||||
/// Instantiate this struct with the given named pipe as a destination.
|
||||
#[cfg(unix)]
|
||||
fn new_fifo(filename: &Path, settings: &'a Settings) -> UResult<Self> {
|
||||
// We simulate seeking in a FIFO by *reading*, so we open the
|
||||
// file for reading. But then we need to close the file and
|
||||
// re-open it for writing.
|
||||
if settings.seek > 0 {
|
||||
Dest::Fifo(File::open(filename)?).seek(settings.seek)?;
|
||||
}
|
||||
// If `count=0`, then we don't bother opening the file for
|
||||
// writing because that would cause this process to block
|
||||
// indefinitely.
|
||||
if let Some(Num::Blocks(0) | Num::Bytes(0)) = settings.count {
|
||||
let dst = Dest::Sink;
|
||||
return Ok(Self { dst, settings });
|
||||
}
|
||||
// At this point, we know there is at least one block to write
|
||||
// to the output, so we open the file for writing.
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.write(true)
|
||||
.create(!settings.oconv.nocreat)
|
||||
.create_new(settings.oconv.excl)
|
||||
.append(settings.oflags.append);
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
opts.custom_flags(make_linux_oflags(&settings.oflags).unwrap_or(0));
|
||||
let dst = Dest::Fifo(opts.open(filename)?);
|
||||
Ok(Self { dst, settings })
|
||||
}
|
||||
|
||||
/// Write the given bytes one block at a time.
|
||||
///
|
||||
/// This may write partial blocks (for example, if the underlying
|
||||
|
@ -473,136 +579,135 @@ impl<'a> Output<'a> {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy the given input data to this output, consuming both.
|
||||
///
|
||||
/// This method contains the main loop for the `dd` program. Bytes
|
||||
/// are read in blocks from `i` and written in blocks to this
|
||||
/// output. Read/write statistics are reported to stderr as
|
||||
/// configured by the `status` command-line argument.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If there is a problem reading from the input or writing to
|
||||
/// this output.
|
||||
fn dd_out<R: Read>(mut self, mut i: Input<R>) -> std::io::Result<()> {
|
||||
// The read and write statistics.
|
||||
/// Copy the given input data to this output, consuming both.
|
||||
///
|
||||
/// This method contains the main loop for the `dd` program. Bytes
|
||||
/// are read in blocks from `i` and written in blocks to this
|
||||
/// output. Read/write statistics are reported to stderr as
|
||||
/// configured by the `status` command-line argument.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If there is a problem reading from the input or writing to
|
||||
/// this output.
|
||||
fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
|
||||
// The read and write statistics.
|
||||
//
|
||||
// These objects are counters, initialized to zero. After each
|
||||
// iteration of the main loop, each will be incremented by the
|
||||
// number of blocks read and written, respectively.
|
||||
let mut rstat = ReadStat::default();
|
||||
let mut wstat = WriteStat::default();
|
||||
|
||||
// The time at which the main loop starts executing.
|
||||
//
|
||||
// When `status=progress` is given on the command-line, the
|
||||
// `dd` program reports its progress every second or so. Part
|
||||
// of its report includes the throughput in bytes per second,
|
||||
// which requires knowing how long the process has been
|
||||
// running.
|
||||
let start = time::Instant::now();
|
||||
|
||||
// A good buffer size for reading.
|
||||
//
|
||||
// This is an educated guess about a good buffer size based on
|
||||
// the input and output block sizes.
|
||||
let bsize = calc_bsize(i.settings.ibs, o.settings.obs);
|
||||
|
||||
// Start a thread that reports transfer progress.
|
||||
//
|
||||
// The `dd` program reports its progress after every block is written,
|
||||
// at most every 1 second, and only if `status=progress` is given on
|
||||
// the command-line or a SIGUSR1 signal is received. We
|
||||
// perform this reporting in a new thread so as not to take
|
||||
// any CPU time away from the actual reading and writing of
|
||||
// data. We send a `ProgUpdate` from the transmitter `prog_tx`
|
||||
// to the receives `rx`, and the receiver prints the transfer
|
||||
// information.
|
||||
let (prog_tx, rx) = mpsc::channel();
|
||||
let output_thread = thread::spawn(gen_prog_updater(rx, i.settings.status));
|
||||
let mut progress_as_secs = 0;
|
||||
|
||||
// Optimization: if no blocks are to be written, then don't
|
||||
// bother allocating any buffers.
|
||||
if let Some(Num::Blocks(0) | Num::Bytes(0)) = i.settings.count {
|
||||
return finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread);
|
||||
};
|
||||
|
||||
// Create a common buffer with a capacity of the block size.
|
||||
// This is the max size needed.
|
||||
let mut buf = vec![BUF_INIT_BYTE; bsize];
|
||||
|
||||
// The main read/write loop.
|
||||
//
|
||||
// Each iteration reads blocks from the input and writes
|
||||
// blocks to this output. Read/write statistics are updated on
|
||||
// each iteration and cumulative statistics are reported to
|
||||
// the progress reporting thread.
|
||||
while below_count_limit(&i.settings.count, &rstat, &wstat) {
|
||||
// Read a block from the input then write the block to the output.
|
||||
//
|
||||
// These objects are counters, initialized to zero. After each
|
||||
// iteration of the main loop, each will be incremented by the
|
||||
// number of blocks read and written, respectively.
|
||||
let mut rstat = ReadStat::default();
|
||||
let mut wstat = WriteStat::default();
|
||||
|
||||
// The time at which the main loop starts executing.
|
||||
//
|
||||
// When `status=progress` is given on the command-line, the
|
||||
// `dd` program reports its progress every second or so. Part
|
||||
// of its report includes the throughput in bytes per second,
|
||||
// which requires knowing how long the process has been
|
||||
// running.
|
||||
let start = time::Instant::now();
|
||||
|
||||
// A good buffer size for reading.
|
||||
//
|
||||
// This is an educated guess about a good buffer size based on
|
||||
// the input and output block sizes.
|
||||
let bsize = calc_bsize(i.settings.ibs, self.settings.obs);
|
||||
|
||||
// Start a thread that reports transfer progress.
|
||||
//
|
||||
// The `dd` program reports its progress after every block is written,
|
||||
// at most every 1 second, and only if `status=progress` is given on
|
||||
// the command-line or a SIGUSR1 signal is received. We
|
||||
// perform this reporting in a new thread so as not to take
|
||||
// any CPU time away from the actual reading and writing of
|
||||
// data. We send a `ProgUpdate` from the transmitter `prog_tx`
|
||||
// to the receives `rx`, and the receiver prints the transfer
|
||||
// information.
|
||||
let (prog_tx, rx) = mpsc::channel();
|
||||
let output_thread = thread::spawn(gen_prog_updater(rx, i.settings.status));
|
||||
let mut progress_as_secs = 0;
|
||||
|
||||
// Optimization: if no blocks are to be written, then don't
|
||||
// bother allocating any buffers.
|
||||
if let Some(Num::Blocks(0) | Num::Bytes(0)) = i.settings.count {
|
||||
return self.finalize(rstat, wstat, start, &prog_tx, output_thread);
|
||||
};
|
||||
|
||||
// Create a common buffer with a capacity of the block size.
|
||||
// This is the max size needed.
|
||||
let mut buf = vec![BUF_INIT_BYTE; bsize];
|
||||
|
||||
// The main read/write loop.
|
||||
//
|
||||
// Each iteration reads blocks from the input and writes
|
||||
// blocks to this output. Read/write statistics are updated on
|
||||
// each iteration and cumulative statistics are reported to
|
||||
// the progress reporting thread.
|
||||
while below_count_limit(&i.settings.count, &rstat, &wstat) {
|
||||
// Read a block from the input then write the block to the output.
|
||||
//
|
||||
// As an optimization, make an educated guess about the
|
||||
// best buffer size for reading based on the number of
|
||||
// blocks already read and the number of blocks remaining.
|
||||
let loop_bsize =
|
||||
calc_loop_bsize(&i.settings.count, &rstat, &wstat, i.settings.ibs, bsize);
|
||||
let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?;
|
||||
if rstat_update.is_empty() {
|
||||
break;
|
||||
}
|
||||
let wstat_update = self.write_blocks(&buf)?;
|
||||
|
||||
// Update the read/write stats and inform the progress thread once per second.
|
||||
//
|
||||
// If the receiver is disconnected, `send()` returns an
|
||||
// error. Since it is just reporting progress and is not
|
||||
// crucial to the operation of `dd`, let's just ignore the
|
||||
// error.
|
||||
rstat += rstat_update;
|
||||
wstat += wstat_update;
|
||||
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), false);
|
||||
if prog_update.duration.as_secs() >= progress_as_secs {
|
||||
progress_as_secs = prog_update.duration.as_secs() + 1;
|
||||
prog_tx.send(prog_update).unwrap_or(());
|
||||
}
|
||||
// As an optimization, make an educated guess about the
|
||||
// best buffer size for reading based on the number of
|
||||
// blocks already read and the number of blocks remaining.
|
||||
let loop_bsize = calc_loop_bsize(&i.settings.count, &rstat, &wstat, i.settings.ibs, bsize);
|
||||
let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?;
|
||||
if rstat_update.is_empty() {
|
||||
break;
|
||||
}
|
||||
self.finalize(rstat, wstat, start, &prog_tx, output_thread)
|
||||
let wstat_update = o.write_blocks(&buf)?;
|
||||
|
||||
// Update the read/write stats and inform the progress thread once per second.
|
||||
//
|
||||
// If the receiver is disconnected, `send()` returns an
|
||||
// error. Since it is just reporting progress and is not
|
||||
// crucial to the operation of `dd`, let's just ignore the
|
||||
// error.
|
||||
rstat += rstat_update;
|
||||
wstat += wstat_update;
|
||||
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), false);
|
||||
if prog_update.duration.as_secs() >= progress_as_secs {
|
||||
progress_as_secs = prog_update.duration.as_secs() + 1;
|
||||
prog_tx.send(prog_update).unwrap_or(());
|
||||
}
|
||||
}
|
||||
finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread)
|
||||
}
|
||||
|
||||
/// Flush output, print final stats, and join with the progress thread.
|
||||
fn finalize<T>(
|
||||
output: &mut Output,
|
||||
rstat: ReadStat,
|
||||
wstat: WriteStat,
|
||||
start: time::Instant,
|
||||
prog_tx: &mpsc::Sender<ProgUpdate>,
|
||||
output_thread: thread::JoinHandle<T>,
|
||||
) -> std::io::Result<()> {
|
||||
// Flush the output, if configured to do so.
|
||||
output.sync()?;
|
||||
|
||||
// Truncate the file to the final cursor location.
|
||||
//
|
||||
// Calling `set_len()` may result in an error (for example,
|
||||
// when calling it on `/dev/null`), but we don't want to
|
||||
// terminate the process when that happens. Instead, we
|
||||
// suppress the error by calling `Result::ok()`. This matches
|
||||
// the behavior of GNU `dd` when given the command-line
|
||||
// argument `of=/dev/null`.
|
||||
if !output.settings.oconv.notrunc {
|
||||
output.dst.truncate().ok();
|
||||
}
|
||||
|
||||
/// Flush output, print final stats, and join with the progress thread.
|
||||
fn finalize<T>(
|
||||
&mut self,
|
||||
rstat: ReadStat,
|
||||
wstat: WriteStat,
|
||||
start: time::Instant,
|
||||
prog_tx: &mpsc::Sender<ProgUpdate>,
|
||||
output_thread: thread::JoinHandle<T>,
|
||||
) -> std::io::Result<()> {
|
||||
// Flush the output, if configured to do so.
|
||||
self.sync()?;
|
||||
|
||||
// Truncate the file to the final cursor location.
|
||||
//
|
||||
// Calling `set_len()` may result in an error (for example,
|
||||
// when calling it on `/dev/null`), but we don't want to
|
||||
// terminate the process when that happens. Instead, we
|
||||
// suppress the error by calling `Result::ok()`. This matches
|
||||
// the behavior of GNU `dd` when given the command-line
|
||||
// argument `of=/dev/null`.
|
||||
if !self.settings.oconv.notrunc {
|
||||
self.dst.truncate().ok();
|
||||
}
|
||||
|
||||
// Print the final read/write statistics.
|
||||
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true);
|
||||
prog_tx.send(prog_update).unwrap_or(());
|
||||
// Wait for the output thread to finish
|
||||
output_thread
|
||||
.join()
|
||||
.expect("Failed to join with the output thread.");
|
||||
Ok(())
|
||||
}
|
||||
// Print the final read/write statistics.
|
||||
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true);
|
||||
prog_tx.send(prog_update).unwrap_or(());
|
||||
// Wait for the output thread to finish
|
||||
output_thread
|
||||
.join()
|
||||
.expect("Failed to join with the output thread.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
|
@ -645,12 +750,13 @@ fn make_linux_oflags(oflags: &OFlags) -> Option<libc::c_int> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read helper performs read operations common to all dd reads, and dispatches the buffer to relevant helper functions as dictated by the operations requested by the user.
|
||||
fn read_helper<R: Read>(
|
||||
i: &mut Input<R>,
|
||||
buf: &mut Vec<u8>,
|
||||
bsize: usize,
|
||||
) -> std::io::Result<ReadStat> {
|
||||
/// Read from an input (that is, a source of bytes) into the given buffer.
|
||||
///
|
||||
/// This function also performs any conversions as specified by
|
||||
/// `conv=swab` or `conv=block` command-line arguments. This function
|
||||
/// mutates the `buf` argument in-place. The returned [`ReadStat`]
|
||||
/// indicates how many blocks were read.
|
||||
fn read_helper(i: &mut Input, buf: &mut Vec<u8>, bsize: usize) -> std::io::Result<ReadStat> {
|
||||
// Local Helper Fns -------------------------------------------------
|
||||
fn perform_swab(buf: &mut [u8]) {
|
||||
for base in (1..buf.len()).step_by(2) {
|
||||
|
@ -778,6 +884,17 @@ fn is_stdout_redirected_to_seekable_file() -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Decide whether the named file is a named pipe, also known as a FIFO.
|
||||
#[cfg(unix)]
|
||||
fn is_fifo(filename: &str) -> bool {
|
||||
if let Ok(metadata) = std::fs::metadata(filename) {
|
||||
if metadata.file_type().is_fifo() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args.collect_ignore();
|
||||
|
@ -792,40 +909,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
.collect::<Vec<_>>()[..],
|
||||
)?;
|
||||
|
||||
match (&settings.infile, &settings.outfile) {
|
||||
(Some(infile), Some(outfile)) => {
|
||||
let i = Input::<File>::new(Path::new(&infile), &settings)?;
|
||||
let o = Output::new_file(Path::new(&outfile), &settings)?;
|
||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
||||
let i = match settings.infile {
|
||||
#[cfg(unix)]
|
||||
Some(ref infile) if is_fifo(infile) => Input::new_fifo(Path::new(&infile), &settings)?,
|
||||
Some(ref infile) => Input::new_file(Path::new(&infile), &settings)?,
|
||||
None => Input::new_stdin(&settings)?,
|
||||
};
|
||||
let o = match settings.outfile {
|
||||
#[cfg(unix)]
|
||||
Some(ref outfile) if is_fifo(outfile) => Output::new_fifo(Path::new(&outfile), &settings)?,
|
||||
Some(ref outfile) => Output::new_file(Path::new(&outfile), &settings)?,
|
||||
None if is_stdout_redirected_to_seekable_file() => {
|
||||
Output::new_file(Path::new(&stdout_canonicalized()), &settings)?
|
||||
}
|
||||
(None, Some(outfile)) => {
|
||||
let i = Input::<io::Stdin>::new(&settings)?;
|
||||
let o = Output::new_file(Path::new(&outfile), &settings)?;
|
||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
||||
}
|
||||
(Some(infile), None) => {
|
||||
let i = Input::<File>::new(Path::new(&infile), &settings)?;
|
||||
if is_stdout_redirected_to_seekable_file() {
|
||||
let filename = stdout_canonicalized();
|
||||
let o = Output::new_file(Path::new(&filename), &settings)?;
|
||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
||||
} else {
|
||||
let o = Output::new_stdout(&settings)?;
|
||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
||||
}
|
||||
}
|
||||
(None, None) => {
|
||||
let i = Input::<io::Stdin>::new(&settings)?;
|
||||
if is_stdout_redirected_to_seekable_file() {
|
||||
let filename = stdout_canonicalized();
|
||||
let o = Output::new_file(Path::new(&filename), &settings)?;
|
||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
||||
} else {
|
||||
let o = Output::new_stdout(&settings)?;
|
||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Output::new_stdout(&settings)?,
|
||||
};
|
||||
dd_copy(i, o).map_err_context(|| "IO error".to_string())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
|
|
18
src/uu/df/df.md
Normal file
18
src/uu/df/df.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# df
|
||||
|
||||
```
|
||||
df [OPTION]... [FILE]...
|
||||
```
|
||||
|
||||
Show information about the file system on which each FILE resides,
|
||||
or all file systems by default.
|
||||
|
||||
## After Help
|
||||
|
||||
Display values are in units of the first available SIZE from --block-size,
|
||||
and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.
|
||||
Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).
|
||||
|
||||
SIZE is an integer and optional unit (example: 10M is 10*1024*1024).
|
||||
Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers
|
||||
of 1000).
|
|
@ -19,7 +19,7 @@ use uucore::error::FromIo;
|
|||
use uucore::error::{UError, UResult, USimpleError};
|
||||
use uucore::fsext::{read_fs_list, MountInfo};
|
||||
use uucore::parse_size::ParseSizeError;
|
||||
use uucore::{format_usage, show};
|
||||
use uucore::{format_usage, help_about, help_section, help_usage, show};
|
||||
|
||||
use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
|
||||
|
||||
|
@ -33,16 +33,9 @@ use crate::columns::{Column, ColumnError};
|
|||
use crate::filesystem::Filesystem;
|
||||
use crate::table::Table;
|
||||
|
||||
static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
|
||||
or all file systems by default.";
|
||||
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
||||
const LONG_HELP: &str = "Display values are in units of the first available SIZE from --block-size,
|
||||
and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.
|
||||
Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).
|
||||
|
||||
SIZE is an integer and optional unit (example: 10M is 10*1024*1024).
|
||||
Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers
|
||||
of 1000).";
|
||||
const ABOUT: &str = help_about!("df.md");
|
||||
const USAGE: &str = help_usage!("df.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "df.md");
|
||||
|
||||
static OPT_HELP: &str = "help";
|
||||
static OPT_ALL: &str = "all";
|
||||
|
@ -487,7 +480,7 @@ pub fn uu_app() -> Command {
|
|||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.infer_long_args(true)
|
||||
.disable_help_flag(true)
|
||||
.arg(
|
||||
|
|
|
@ -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.
|
||||
|
|
13
src/uu/dircolors/dircolors.md
Normal file
13
src/uu/dircolors/dircolors.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# dircolors
|
||||
|
||||
```
|
||||
dircolors [OPTION]... [FILE]
|
||||
```
|
||||
|
||||
Output commands to set the LS_COLORS environment variable.
|
||||
|
||||
## After Help
|
||||
|
||||
If FILE is specified, read it to determine which colors to use for which
|
||||
file types and extensions. Otherwise, a precompiled database is used.
|
||||
For details on the format of these files, run 'dircolors --print-database'
|
|
@ -16,6 +16,7 @@ use std::io::{BufRead, BufReader};
|
|||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||
use uucore::{help_about, help_section, help_usage};
|
||||
|
||||
mod options {
|
||||
pub const BOURNE_SHELL: &str = "bourne-shell";
|
||||
|
@ -25,13 +26,9 @@ mod options {
|
|||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
||||
static USAGE: &str = "{} [OPTION]... [FILE]";
|
||||
static ABOUT: &str = "Output commands to set the LS_COLORS environment variable.";
|
||||
static LONG_HELP: &str = "
|
||||
If FILE is specified, read it to determine which colors to use for which
|
||||
file types and extensions. Otherwise, a precompiled database is used.
|
||||
For details on the format of these files, run 'dircolors --print-database'
|
||||
";
|
||||
const USAGE: &str = help_usage!("dircolors.md");
|
||||
const ABOUT: &str = help_about!("dircolors.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "dircolors.md");
|
||||
|
||||
mod colors;
|
||||
use self::colors::INTERNAL_DB;
|
||||
|
@ -170,7 +167,7 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
.arg(
|
||||
|
|
12
src/uu/dirname/dirname.md
Normal file
12
src/uu/dirname/dirname.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# dirname
|
||||
|
||||
```
|
||||
dirname [OPTION] NAME...
|
||||
```
|
||||
|
||||
Strip last component from file name
|
||||
|
||||
## After Help
|
||||
|
||||
Output each NAME with its last non-slash component and trailing slashes
|
||||
removed; if NAME contains no /'s, output '.' (meaning the current directory).
|
|
@ -9,13 +9,11 @@ use clap::{crate_version, Arg, ArgAction, Command};
|
|||
use std::path::Path;
|
||||
use uucore::display::print_verbatim;
|
||||
use uucore::error::{UResult, UUsageError};
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_section, help_usage};
|
||||
|
||||
const ABOUT: &str = "Strip last component from file name";
|
||||
const USAGE: &str = "{} [OPTION] NAME...";
|
||||
const LONG_USAGE: &str = "\
|
||||
Output each NAME with its last non-slash component and trailing slashes \n\
|
||||
removed; if NAME contains no /'s, output '.' (meaning the current directory).";
|
||||
const ABOUT: &str = help_about!("dirname.md");
|
||||
const USAGE: &str = help_usage!("dirname.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "dirname.md");
|
||||
|
||||
mod options {
|
||||
pub const ZERO: &str = "zero";
|
||||
|
@ -26,7 +24,7 @@ mod options {
|
|||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args.collect_lossy();
|
||||
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?;
|
||||
|
||||
let separator = if matches.get_flag(options::ZERO) {
|
||||
"\0"
|
||||
|
|
24
src/uu/du/du.md
Normal file
24
src/uu/du/du.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# du
|
||||
|
||||
```
|
||||
du [OPTION]... [FILE]...
|
||||
du [OPTION]... --files0-from=F
|
||||
```
|
||||
|
||||
Estimate file space usage
|
||||
|
||||
## After Help
|
||||
|
||||
Display values are in units of the first available SIZE from --block-size,
|
||||
and the DU_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.
|
||||
Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).
|
||||
|
||||
SIZE is an integer and optional unit (example: 10M is 10*1024*1024).
|
||||
Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers
|
||||
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
|
|
@ -36,7 +36,9 @@ use uucore::error::FromIo;
|
|||
use uucore::error::{UError, UResult};
|
||||
use uucore::parse_glob;
|
||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||
use uucore::{crash, format_usage, show, show_error, show_warning};
|
||||
use uucore::{
|
||||
crash, format_usage, help_about, help_section, help_usage, show, show_error, show_warning,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
#[cfg(windows)]
|
||||
|
@ -73,25 +75,9 @@ mod options {
|
|||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
||||
const ABOUT: &str = "Estimate file space usage";
|
||||
const LONG_HELP: &str = "
|
||||
Display values are in units of the first available SIZE from --block-size,
|
||||
and the DU_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.
|
||||
Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).
|
||||
|
||||
SIZE is an integer and optional unit (example: 10M is 10*1024*1024).
|
||||
Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers
|
||||
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
|
||||
";
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... [FILE]...
|
||||
{} [OPTION]... --files0-from=F";
|
||||
const ABOUT: &str = help_about!("du.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "du.md");
|
||||
const USAGE: &str = help_usage!("du.md");
|
||||
|
||||
// TODO: Support Z & Y (currently limited by size of u64)
|
||||
const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)];
|
||||
|
@ -705,7 +691,7 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
.disable_help_flag(true)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
7
src/uu/fmt/fmt.md
Normal file
7
src/uu/fmt/fmt.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# fmt
|
||||
|
||||
```
|
||||
fmt [OPTION]... [FILE]...
|
||||
```
|
||||
|
||||
Reformat paragraphs from input files (or stdin) to stdout.
|
|
@ -14,7 +14,7 @@ use std::io::{stdin, stdout, Write};
|
|||
use std::io::{BufReader, BufWriter, Read};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
use uucore::{format_usage, show_warning};
|
||||
use uucore::{format_usage, help_about, help_usage, show_warning};
|
||||
|
||||
use self::linebreak::break_lines;
|
||||
use self::parasplit::ParagraphStream;
|
||||
|
@ -22,8 +22,8 @@ use self::parasplit::ParagraphStream;
|
|||
mod linebreak;
|
||||
mod parasplit;
|
||||
|
||||
static ABOUT: &str = "Reformat paragraphs from input files (or stdin) to stdout.";
|
||||
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
||||
static ABOUT: &str = help_about!("fmt.md");
|
||||
const USAGE: &str = help_usage!("fmt.md");
|
||||
static MAX_WIDTH: usize = 2500;
|
||||
|
||||
static OPT_CROWN_MARGIN: &str = "crown-margin";
|
||||
|
|
|
@ -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"
|
||||
```
|
||||
|
|
|
@ -15,18 +15,11 @@ edition = "2021"
|
|||
path = "src/hashsum.rs"
|
||||
|
||||
[dependencies]
|
||||
digest = "0.10.6"
|
||||
clap = { workspace=true }
|
||||
hex = "0.4.3"
|
||||
memchr = { workspace=true }
|
||||
md-5 = "0.10.5"
|
||||
regex = { workspace=true }
|
||||
sha1 = "0.10.1"
|
||||
sha2 = "0.10.2"
|
||||
sha3 = "0.10.6"
|
||||
blake2b_simd = "1.0.1"
|
||||
blake3 = "1.3.2"
|
||||
uucore = { workspace=true }
|
||||
memchr = { workspace=true }
|
||||
regex = { workspace=true }
|
||||
hex = { workspace=true }
|
||||
|
||||
[[bin]]
|
||||
name = "hashsum"
|
||||
|
|
|
@ -1,287 +0,0 @@
|
|||
// spell-checker:ignore memmem
|
||||
//! Implementations of digest functions, like md5 and sha1.
|
||||
//!
|
||||
//! The [`Digest`] trait represents the interface for providing inputs
|
||||
//! to these digest functions and accessing the resulting hash. The
|
||||
//! [`DigestWriter`] struct provides a wrapper around [`Digest`] that
|
||||
//! implements the [`Write`] trait, for use in situations where calling
|
||||
//! [`write`] would be useful.
|
||||
use std::io::Write;
|
||||
|
||||
use hex::encode;
|
||||
#[cfg(windows)]
|
||||
use memchr::memmem;
|
||||
|
||||
pub trait Digest {
|
||||
fn new() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn input(&mut self, input: &[u8]);
|
||||
fn result(&mut self, out: &mut [u8]);
|
||||
fn reset(&mut self);
|
||||
fn output_bits(&self) -> usize;
|
||||
fn output_bytes(&self) -> usize {
|
||||
(self.output_bits() + 7) / 8
|
||||
}
|
||||
fn result_str(&mut self) -> String {
|
||||
let mut buf: Vec<u8> = vec![0; self.output_bytes()];
|
||||
self.result(&mut buf);
|
||||
encode(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Digest for blake2b_simd::State {
|
||||
fn new() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
fn input(&mut self, input: &[u8]) {
|
||||
self.update(input);
|
||||
}
|
||||
|
||||
fn result(&mut self, out: &mut [u8]) {
|
||||
let hash_result = &self.finalize();
|
||||
out.copy_from_slice(hash_result.as_bytes());
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
*self = Self::new();
|
||||
}
|
||||
|
||||
fn output_bits(&self) -> usize {
|
||||
512
|
||||
}
|
||||
}
|
||||
|
||||
impl Digest for blake3::Hasher {
|
||||
fn new() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
fn input(&mut self, input: &[u8]) {
|
||||
self.update(input);
|
||||
}
|
||||
|
||||
fn result(&mut self, out: &mut [u8]) {
|
||||
let hash_result = &self.finalize();
|
||||
out.copy_from_slice(hash_result.as_bytes());
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
*self = Self::new();
|
||||
}
|
||||
|
||||
fn output_bits(&self) -> usize {
|
||||
256
|
||||
}
|
||||
}
|
||||
|
||||
// Implements the Digest trait for sha2 / sha3 algorithms with fixed output
|
||||
macro_rules! impl_digest_common {
|
||||
($type: ty, $size: expr) => {
|
||||
impl Digest for $type {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn input(&mut self, input: &[u8]) {
|
||||
digest::Digest::update(self, input);
|
||||
}
|
||||
|
||||
fn result(&mut self, out: &mut [u8]) {
|
||||
digest::Digest::finalize_into_reset(self, out.into());
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
*self = Self::new();
|
||||
}
|
||||
|
||||
fn output_bits(&self) -> usize {
|
||||
$size
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Implements the Digest trait for sha2 / sha3 algorithms with variable output
|
||||
macro_rules! impl_digest_shake {
|
||||
($type: ty) => {
|
||||
impl Digest for $type {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn input(&mut self, input: &[u8]) {
|
||||
digest::Update::update(self, input);
|
||||
}
|
||||
|
||||
fn result(&mut self, out: &mut [u8]) {
|
||||
digest::ExtendableOutputReset::finalize_xof_reset_into(self, out);
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
*self = Self::new();
|
||||
}
|
||||
|
||||
fn output_bits(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_digest_common!(md5::Md5, 128);
|
||||
impl_digest_common!(sha1::Sha1, 160);
|
||||
impl_digest_common!(sha2::Sha224, 224);
|
||||
impl_digest_common!(sha2::Sha256, 256);
|
||||
impl_digest_common!(sha2::Sha384, 384);
|
||||
impl_digest_common!(sha2::Sha512, 512);
|
||||
|
||||
impl_digest_common!(sha3::Sha3_224, 224);
|
||||
impl_digest_common!(sha3::Sha3_256, 256);
|
||||
impl_digest_common!(sha3::Sha3_384, 384);
|
||||
impl_digest_common!(sha3::Sha3_512, 512);
|
||||
impl_digest_shake!(sha3::Shake128);
|
||||
impl_digest_shake!(sha3::Shake256);
|
||||
|
||||
/// A struct that writes to a digest.
|
||||
///
|
||||
/// This struct wraps a [`Digest`] and provides a [`Write`]
|
||||
/// implementation that passes input bytes directly to the
|
||||
/// [`Digest::input`].
|
||||
///
|
||||
/// On Windows, if `binary` is `false`, then the [`write`]
|
||||
/// implementation replaces instances of "\r\n" with "\n" before passing
|
||||
/// the input bytes to the [`digest`].
|
||||
pub struct DigestWriter<'a> {
|
||||
digest: &'a mut Box<dyn Digest>,
|
||||
|
||||
/// Whether to write to the digest in binary mode or text mode on Windows.
|
||||
///
|
||||
/// If this is `false`, then instances of "\r\n" are replaced with
|
||||
/// "\n" before passing input bytes to the [`digest`].
|
||||
#[allow(dead_code)]
|
||||
binary: bool,
|
||||
|
||||
/// Whether the previous
|
||||
#[allow(dead_code)]
|
||||
was_last_character_carriage_return: bool,
|
||||
// TODO These are dead code only on non-Windows operating systems.
|
||||
// It might be better to use a `#[cfg(windows)]` guard here.
|
||||
}
|
||||
|
||||
impl<'a> DigestWriter<'a> {
|
||||
pub fn new(digest: &'a mut Box<dyn Digest>, binary: bool) -> DigestWriter {
|
||||
let was_last_character_carriage_return = false;
|
||||
DigestWriter {
|
||||
digest,
|
||||
binary,
|
||||
was_last_character_carriage_return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self) -> bool {
|
||||
if self.was_last_character_carriage_return {
|
||||
self.digest.input(&[b'\r']);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Write for DigestWriter<'a> {
|
||||
#[cfg(not(windows))]
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.digest.input(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
if self.binary {
|
||||
self.digest.input(buf);
|
||||
return Ok(buf.len());
|
||||
}
|
||||
|
||||
// The remaining code handles Windows text mode, where we must
|
||||
// replace each occurrence of "\r\n" with "\n".
|
||||
//
|
||||
// First, if the last character written was "\r" and the first
|
||||
// character in the current buffer to write is not "\n", then we
|
||||
// need to write the "\r" that we buffered from the previous
|
||||
// call to `write()`.
|
||||
let n = buf.len();
|
||||
if self.was_last_character_carriage_return && n > 0 && buf[0] != b'\n' {
|
||||
self.digest.input(&[b'\r']);
|
||||
}
|
||||
|
||||
// Next, find all occurrences of "\r\n", inputting the slice
|
||||
// just before the "\n" in the previous instance of "\r\n" and
|
||||
// the beginning of this "\r\n".
|
||||
let mut i_prev = 0;
|
||||
for i in memmem::find_iter(buf, b"\r\n") {
|
||||
self.digest.input(&buf[i_prev..i]);
|
||||
i_prev = i + 1;
|
||||
}
|
||||
|
||||
// Finally, check whether the last character is "\r". If so,
|
||||
// buffer it until we know that the next character is not "\n",
|
||||
// which can only be known on the next call to `write()`.
|
||||
//
|
||||
// This all assumes that `write()` will be called on adjacent
|
||||
// blocks of the input.
|
||||
if n > 0 && buf[n - 1] == b'\r' {
|
||||
self.was_last_character_carriage_return = true;
|
||||
self.digest.input(&buf[i_prev..n - 1]);
|
||||
} else {
|
||||
self.was_last_character_carriage_return = false;
|
||||
self.digest.input(&buf[i_prev..n]);
|
||||
}
|
||||
|
||||
// Even though we dropped a "\r" for each "\r\n" we found, we
|
||||
// still report the number of bytes written as `n`. This is
|
||||
// because the meaning of the returned number is supposed to be
|
||||
// the number of bytes consumed by the writer, so that if the
|
||||
// calling code were calling `write()` in a loop, it would know
|
||||
// where the next contiguous slice of the buffer starts.
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
/// Test for replacing a "\r\n" sequence with "\n" when the "\r" is
|
||||
/// at the end of one block and the "\n" is at the beginning of the
|
||||
/// next block, when reading in blocks.
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_crlf_across_blocks() {
|
||||
use std::io::Write;
|
||||
|
||||
use crate::digest::Digest;
|
||||
use crate::digest::DigestWriter;
|
||||
|
||||
// Writing "\r" in one call to `write()`, and then "\n" in another.
|
||||
let mut digest = Box::new(md5::Md5::new()) as Box<dyn Digest>;
|
||||
let mut writer_crlf = DigestWriter::new(&mut digest, false);
|
||||
writer_crlf.write_all(&[b'\r']).unwrap();
|
||||
writer_crlf.write_all(&[b'\n']).unwrap();
|
||||
writer_crlf.finalize();
|
||||
let result_crlf = digest.result_str();
|
||||
|
||||
// We expect "\r\n" to be replaced with "\n" in text mode on Windows.
|
||||
let mut digest = Box::new(md5::Md5::new()) as Box<dyn Digest>;
|
||||
let mut writer_lf = DigestWriter::new(&mut digest, false);
|
||||
writer_lf.write_all(&[b'\n']).unwrap();
|
||||
writer_lf.finalize();
|
||||
let result_lf = digest.result_str();
|
||||
|
||||
assert_eq!(result_crlf, result_lf);
|
||||
}
|
||||
}
|
|
@ -9,21 +9,12 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) algo, algoname, regexes, nread, nonames
|
||||
|
||||
mod digest;
|
||||
|
||||
use self::digest::Digest;
|
||||
use self::digest::DigestWriter;
|
||||
|
||||
use clap::builder::ValueParser;
|
||||
use clap::crate_version;
|
||||
use clap::ArgAction;
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use hex::encode;
|
||||
use md5::Md5;
|
||||
use regex::Regex;
|
||||
use sha1::Sha1;
|
||||
use sha2::{Sha224, Sha256, Sha384, Sha512};
|
||||
use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256};
|
||||
use std::cmp::Ordering;
|
||||
use std::error::Error;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
@ -32,10 +23,12 @@ use std::io::{self, stdin, BufRead, BufReader, Read};
|
|||
use std::iter;
|
||||
use std::num::ParseIntError;
|
||||
use std::path::Path;
|
||||
use uucore::crash;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UError, UResult};
|
||||
use uucore::show_warning;
|
||||
use uucore::sum::{
|
||||
Blake2b, Blake3, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha3_224, Sha3_256,
|
||||
Sha3_384, Sha3_512, Sha512, Shake128, Shake256,
|
||||
};
|
||||
use uucore::{crash, display::Quotable, show_warning};
|
||||
|
||||
const NAME: &str = "hashsum";
|
||||
|
||||
|
@ -68,16 +61,8 @@ fn detect_algo(
|
|||
"sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box<dyn Digest>, 256),
|
||||
"sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box<dyn Digest>, 384),
|
||||
"sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512),
|
||||
"b2sum" => (
|
||||
"BLAKE2",
|
||||
Box::new(blake2b_simd::State::new()) as Box<dyn Digest>,
|
||||
512,
|
||||
),
|
||||
"b3sum" => (
|
||||
"BLAKE3",
|
||||
Box::new(blake3::Hasher::new()) as Box<dyn Digest>,
|
||||
256,
|
||||
),
|
||||
"b2sum" => ("BLAKE2", Box::new(Blake2b::new()) as Box<dyn Digest>, 512),
|
||||
"b3sum" => ("BLAKE3", Box::new(Blake3::new()) as Box<dyn Digest>, 256),
|
||||
"sha3sum" => match matches.get_one::<usize>("bits") {
|
||||
Some(224) => (
|
||||
"SHA3-224",
|
||||
|
@ -170,10 +155,10 @@ fn detect_algo(
|
|||
set_or_crash("SHA512", Box::new(Sha512::new()), 512);
|
||||
}
|
||||
if matches.get_flag("b2sum") {
|
||||
set_or_crash("BLAKE2", Box::new(blake2b_simd::State::new()), 512);
|
||||
set_or_crash("BLAKE2", Box::new(Blake2b::new()), 512);
|
||||
}
|
||||
if matches.get_flag("b3sum") {
|
||||
set_or_crash("BLAKE3", Box::new(blake3::Hasher::new()), 256);
|
||||
set_or_crash("BLAKE3", Box::new(Blake3::new()), 256);
|
||||
}
|
||||
if matches.get_flag("sha3") {
|
||||
match matches.get_one::<usize>("bits") {
|
||||
|
@ -680,7 +665,7 @@ fn digest_reader<T: Read>(
|
|||
// Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016)
|
||||
let mut bytes = Vec::new();
|
||||
bytes.resize((output_bits + 7) / 8, 0);
|
||||
digest.result(&mut bytes);
|
||||
digest.hash_finalize(&mut bytes);
|
||||
Ok(encode(bytes))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -41,8 +41,8 @@ pub struct Behavior {
|
|||
specified_mode: Option<u32>,
|
||||
backup_mode: BackupMode,
|
||||
suffix: String,
|
||||
owner: String,
|
||||
group: String,
|
||||
owner_id: Option<u32>,
|
||||
group_id: Option<u32>,
|
||||
verbose: bool,
|
||||
preserve_timestamps: bool,
|
||||
compare: bool,
|
||||
|
@ -58,14 +58,15 @@ enum InstallError {
|
|||
DirNeedsArg(),
|
||||
CreateDirFailed(PathBuf, std::io::Error),
|
||||
ChmodFailed(PathBuf),
|
||||
ChownFailed(PathBuf, String),
|
||||
InvalidTarget(PathBuf),
|
||||
TargetDirIsntDir(PathBuf),
|
||||
BackupFailed(PathBuf, PathBuf, std::io::Error),
|
||||
InstallFailed(PathBuf, PathBuf, std::io::Error),
|
||||
StripProgramFailed(String),
|
||||
MetadataFailed(std::io::Error),
|
||||
NoSuchUser(String),
|
||||
NoSuchGroup(String),
|
||||
InvalidUser(String),
|
||||
InvalidGroup(String),
|
||||
OmittingDirectory(PathBuf),
|
||||
}
|
||||
|
||||
|
@ -99,6 +100,7 @@ impl Display for InstallError {
|
|||
Display::fmt(&uio_error!(e, "failed to create {}", dir.quote()), f)
|
||||
}
|
||||
Self::ChmodFailed(file) => write!(f, "failed to chmod {}", file.quote()),
|
||||
Self::ChownFailed(file, msg) => write!(f, "failed to chown {}: {}", file.quote(), msg),
|
||||
Self::InvalidTarget(target) => write!(
|
||||
f,
|
||||
"invalid target {}: No such file or directory",
|
||||
|
@ -117,8 +119,8 @@ impl Display for InstallError {
|
|||
),
|
||||
Self::StripProgramFailed(msg) => write!(f, "strip program failed: {msg}"),
|
||||
Self::MetadataFailed(e) => Display::fmt(&uio_error!(e, ""), f),
|
||||
Self::NoSuchUser(user) => write!(f, "no such user: {}", user.maybe_quote()),
|
||||
Self::NoSuchGroup(group) => write!(f, "no such group: {}", group.maybe_quote()),
|
||||
Self::InvalidUser(user) => write!(f, "invalid user: {}", user.quote()),
|
||||
Self::InvalidGroup(group) => write!(f, "invalid group: {}", group.quote()),
|
||||
Self::OmittingDirectory(dir) => write!(f, "omitting directory {}", dir.quote()),
|
||||
}
|
||||
}
|
||||
|
@ -391,21 +393,44 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
|
|||
show_error!("Options --compare and --strip are mutually exclusive");
|
||||
return Err(1.into());
|
||||
}
|
||||
|
||||
let owner = matches
|
||||
.get_one::<String>(OPT_OWNER)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let owner_id = if !owner.is_empty() {
|
||||
match usr2uid(&owner) {
|
||||
Ok(u) => Some(u),
|
||||
Err(_) => return Err(InstallError::InvalidUser(owner.clone()).into()),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let group = matches
|
||||
.get_one::<String>(OPT_GROUP)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let group_id = if !group.is_empty() {
|
||||
match grp2gid(&group) {
|
||||
Ok(g) => Some(g),
|
||||
Err(_) => return Err(InstallError::InvalidGroup(group.clone()).into()),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Behavior {
|
||||
main_function,
|
||||
specified_mode,
|
||||
backup_mode,
|
||||
suffix: backup_control::determine_backup_suffix(matches),
|
||||
owner: matches
|
||||
.get_one::<String>(OPT_OWNER)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
group: matches
|
||||
.get_one::<String>(OPT_GROUP)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
owner_id,
|
||||
group_id,
|
||||
verbose: matches.get_flag(OPT_VERBOSE),
|
||||
preserve_timestamps,
|
||||
compare,
|
||||
|
@ -466,6 +491,8 @@ fn directory(paths: &[String], b: &Behavior) -> UResult<()> {
|
|||
uucore::error::set_exit_code(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
show_if_err!(chown_optional_user_group(path, b));
|
||||
}
|
||||
// If the exit code was set, or show! has been called at least once
|
||||
// (which sets the exit code as well), function execution will end after
|
||||
|
@ -620,6 +647,42 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle incomplete user/group parings for chown.
|
||||
///
|
||||
/// Returns a Result type with the Err variant containing the error message.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// _path_ must exist.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If the owner or group are invalid or copy system call fails, we print a verbose error and
|
||||
/// return an empty error value.
|
||||
///
|
||||
fn chown_optional_user_group(path: &Path, b: &Behavior) -> UResult<()> {
|
||||
if b.owner_id.is_some() || b.group_id.is_some() {
|
||||
let meta = match fs::metadata(path) {
|
||||
Ok(meta) => meta,
|
||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||
};
|
||||
|
||||
// GNU coreutils doesn't print chown operations during install with verbose flag.
|
||||
let verbosity = Verbosity {
|
||||
groups_only: b.owner_id.is_none(),
|
||||
level: VerbosityLevel::Normal,
|
||||
};
|
||||
|
||||
match wrap_chown(path, &meta, b.owner_id, b.group_id, false, verbosity) {
|
||||
Ok(msg) if b.verbose && !msg.is_empty() => println!("chown: {msg}"),
|
||||
Ok(_) => {}
|
||||
Err(e) => return Err(InstallError::ChownFailed(path.to_path_buf(), e).into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy one file to a new location, changing metadata.
|
||||
///
|
||||
/// Returns a Result type with the Err variant containing the error message.
|
||||
|
@ -702,66 +765,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
|
|||
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
|
||||
}
|
||||
|
||||
if !b.owner.is_empty() {
|
||||
let meta = match fs::metadata(to) {
|
||||
Ok(meta) => meta,
|
||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||
};
|
||||
|
||||
let owner_id = match usr2uid(&b.owner) {
|
||||
Ok(g) => g,
|
||||
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
||||
};
|
||||
let gid = meta.gid();
|
||||
match wrap_chown(
|
||||
to,
|
||||
&meta,
|
||||
Some(owner_id),
|
||||
Some(gid),
|
||||
false,
|
||||
Verbosity {
|
||||
groups_only: false,
|
||||
level: VerbosityLevel::Normal,
|
||||
},
|
||||
) {
|
||||
Ok(n) => {
|
||||
if !n.is_empty() {
|
||||
show_error!("{}", n);
|
||||
}
|
||||
}
|
||||
Err(e) => show_error!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
if !b.group.is_empty() {
|
||||
let meta = match fs::metadata(to) {
|
||||
Ok(meta) => meta,
|
||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||
};
|
||||
|
||||
let group_id = match grp2gid(&b.group) {
|
||||
Ok(g) => g,
|
||||
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
||||
};
|
||||
match wrap_chown(
|
||||
to,
|
||||
&meta,
|
||||
Some(group_id),
|
||||
None,
|
||||
false,
|
||||
Verbosity {
|
||||
groups_only: true,
|
||||
level: VerbosityLevel::Normal,
|
||||
},
|
||||
) {
|
||||
Ok(n) => {
|
||||
if !n.is_empty() {
|
||||
show_error!("{}", n);
|
||||
}
|
||||
}
|
||||
Err(e) => show_error!("{}", e),
|
||||
}
|
||||
}
|
||||
chown_optional_user_group(to, b)?;
|
||||
|
||||
if b.preserve_timestamps {
|
||||
let meta = match fs::metadata(from) {
|
||||
|
@ -841,19 +845,11 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> UResult<bool> {
|
|||
|
||||
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
|
||||
|
||||
if !b.owner.is_empty() {
|
||||
let owner_id = match usr2uid(&b.owner) {
|
||||
Ok(id) => id,
|
||||
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
||||
};
|
||||
if let Some(owner_id) = b.owner_id {
|
||||
if owner_id != to_meta.uid() {
|
||||
return Ok(true);
|
||||
}
|
||||
} else if !b.group.is_empty() {
|
||||
let group_id = match grp2gid(&b.group) {
|
||||
Ok(id) => id,
|
||||
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
||||
};
|
||||
} else if let Some(group_id) = b.group_id {
|
||||
if group_id != to_meta.gid() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
7
src/uu/kill/kill.md
Normal file
7
src/uu/kill/kill.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# kill
|
||||
|
||||
```
|
||||
kill [OPTIONS]... PID...
|
||||
```
|
||||
|
||||
Send signal to processes or list information about signals.
|
|
@ -14,10 +14,10 @@ use std::io::Error;
|
|||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||
use uucore::signals::{signal_by_name_or_value, ALL_SIGNALS};
|
||||
use uucore::{format_usage, show};
|
||||
use uucore::{format_usage, help_about, help_usage, show};
|
||||
|
||||
static ABOUT: &str = "Send signal to processes or list information about signals.";
|
||||
const USAGE: &str = "{} [OPTIONS]... PID...";
|
||||
static ABOUT: &str = help_about!("kill.md");
|
||||
const USAGE: &str = help_usage!("kill.md");
|
||||
|
||||
pub mod options {
|
||||
pub static PIDS_OR_SIGNALS: &str = "pids_or_signals";
|
||||
|
|
7
src/uu/link/link.md
Normal file
7
src/uu/link/link.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# link
|
||||
|
||||
```
|
||||
link FILE1 FILE2
|
||||
```
|
||||
|
||||
Call the link function to create a link named FILE2 to an existing FILE1.
|
|
@ -11,10 +11,10 @@ use std::fs::hard_link;
|
|||
use std::path::Path;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1.";
|
||||
const USAGE: &str = "{} FILE1 FILE2";
|
||||
static ABOUT: &str = help_about!("link.md");
|
||||
const USAGE: &str = help_usage!("link.md");
|
||||
|
||||
pub mod options {
|
||||
pub static FILES: &str = "FILES";
|
||||
|
|
21
src/uu/ln/ln.md
Normal file
21
src/uu/ln/ln.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# ln
|
||||
|
||||
```
|
||||
ln [OPTION]... [-T] TARGET LINK_NAME
|
||||
ln [OPTION]... TARGET
|
||||
ln [OPTION]... TARGET... DIRECTORY
|
||||
ln [OPTION]... -t DIRECTORY TARGET...
|
||||
```
|
||||
|
||||
Change file owner and group
|
||||
|
||||
## After Help
|
||||
|
||||
In the 1st form, create a link to TARGET with the name LINK_NAME.
|
||||
In the 2nd form, create a link to TARGET in the current directory.
|
||||
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.
|
||||
Create hard links by default, symbolic links with --symbolic.
|
||||
By default, each destination (name of new link) should not already exist.
|
||||
When creating hard links, each TARGET must exist. Symbolic links
|
||||
can hold arbitrary text; if later resolved, a relative link is
|
||||
interpreted in relation to its parent directory.
|
|
@ -11,7 +11,7 @@ use clap::{crate_version, Arg, ArgAction, Command};
|
|||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UError, UResult};
|
||||
use uucore::fs::{make_path_relative_to, paths_refer_to_same_file};
|
||||
use uucore::{format_usage, prompt_yes, show_error};
|
||||
use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show_error};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
|
@ -85,21 +85,9 @@ impl UError for LnError {
|
|||
}
|
||||
}
|
||||
|
||||
const ABOUT: &str = "Change file owner and group";
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... [-T] TARGET LINK_NAME
|
||||
{} [OPTION]... TARGET
|
||||
{} [OPTION]... TARGET... DIRECTORY
|
||||
{} [OPTION]... -t DIRECTORY TARGET...";
|
||||
const LONG_USAGE: &str = "\
|
||||
In the 1st form, create a link to TARGET with the name LINK_NAME.\n\
|
||||
In the 2nd form, create a link to TARGET in the current directory.\n\
|
||||
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.\n\
|
||||
Create hard links by default, symbolic links with --symbolic.\n\
|
||||
By default, each destination (name of new link) should not already exist.\n\
|
||||
When creating hard links, each TARGET must exist. Symbolic links\n\
|
||||
can hold arbitrary text; if later resolved, a relative link is\n\
|
||||
interpreted in relation to its parent directory.";
|
||||
const ABOUT: &str = help_about!("ln.md");
|
||||
const USAGE: &str = help_usage!("ln.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "ln.md");
|
||||
|
||||
mod options {
|
||||
pub const FORCE: &str = "force";
|
||||
|
@ -119,13 +107,13 @@ static ARG_FILES: &str = "files";
|
|||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let long_usage = format!(
|
||||
let after_help = format!(
|
||||
"{}\n\n{}",
|
||||
LONG_USAGE,
|
||||
AFTER_HELP,
|
||||
backup_control::BACKUP_CONTROL_LONG_HELP
|
||||
);
|
||||
|
||||
let matches = uu_app().after_help(long_usage).try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(after_help).try_get_matches_from(args)?;
|
||||
|
||||
/* the list of files */
|
||||
|
||||
|
|
|
@ -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 "$@"
|
||||
|
|
|
@ -25,7 +25,7 @@ glob = { workspace=true }
|
|||
lscolors = { workspace=true }
|
||||
uucore = { workspace=true, features = ["entries", "fs"] }
|
||||
once_cell = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace=true }
|
||||
selinux = { workspace=true, optional = true }
|
||||
|
||||
[[bin]]
|
||||
|
|
12
src/uu/ls/ls.md
Normal file
12
src/uu/ls/ls.md
Normal 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.
|
|
@ -12,6 +12,7 @@ use clap::{
|
|||
crate_version, Arg, ArgAction, Command,
|
||||
};
|
||||
use glob::{MatchOptions, Pattern};
|
||||
use is_terminal::IsTerminal;
|
||||
use lscolors::LsColors;
|
||||
use number_prefix::NumberPrefix;
|
||||
use once_cell::unsync::OnceCell;
|
||||
|
@ -54,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 {
|
||||
|
@ -451,7 +451,7 @@ impl Config {
|
|||
(Format::Commas, Some(options::format::COMMAS))
|
||||
} else if options.get_flag(options::format::COLUMNS) {
|
||||
(Format::Columns, Some(options::format::COLUMNS))
|
||||
} else if atty::is(atty::Stream::Stdout) {
|
||||
} else if std::io::stdout().is_terminal() {
|
||||
(Format::Columns, None)
|
||||
} else {
|
||||
(Format::OneLine, None)
|
||||
|
@ -557,7 +557,7 @@ impl Config {
|
|||
None => options.contains_id(options::COLOR),
|
||||
Some(val) => match val.as_str() {
|
||||
"" | "always" | "yes" | "force" => true,
|
||||
"auto" | "tty" | "if-tty" => atty::is(atty::Stream::Stdout),
|
||||
"auto" | "tty" | "if-tty" => std::io::stdout().is_terminal(),
|
||||
/* "never" | "no" | "none" | */ _ => false,
|
||||
},
|
||||
};
|
||||
|
@ -678,7 +678,7 @@ impl Config {
|
|||
} else if options.get_flag(options::SHOW_CONTROL_CHARS) {
|
||||
true
|
||||
} else {
|
||||
!atty::is(atty::Stream::Stdout)
|
||||
!std::io::stdout().is_terminal()
|
||||
};
|
||||
|
||||
let opt_quoting_style = options
|
||||
|
@ -750,7 +750,7 @@ impl Config {
|
|||
"never" | "no" | "none" => IndicatorStyle::None,
|
||||
"always" | "yes" | "force" => IndicatorStyle::Classify,
|
||||
"auto" | "tty" | "if-tty" => {
|
||||
if atty::is(atty::Stream::Stdout) {
|
||||
if std::io::stdout().is_terminal() {
|
||||
IndicatorStyle::Classify
|
||||
} else {
|
||||
IndicatorStyle::None
|
||||
|
@ -1620,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.
|
||||
|
|
13
src/uu/mkdir/mkdir.md
Normal file
13
src/uu/mkdir/mkdir.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# mkdir
|
||||
|
||||
<!-- spell-checker:ignore ugoa -->
|
||||
|
||||
```
|
||||
mkdir [OPTION]... [USER]
|
||||
```
|
||||
|
||||
Create the given DIRECTORY(ies) if they do not exist
|
||||
|
||||
## After Help
|
||||
|
||||
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.
|
|
@ -18,14 +18,13 @@ use uucore::error::{UResult, USimpleError};
|
|||
#[cfg(not(windows))]
|
||||
use uucore::mode;
|
||||
use uucore::{display::Quotable, fs::dir_strip_dot_for_creation};
|
||||
use uucore::{format_usage, show, show_if_err};
|
||||
use uucore::{format_usage, help_about, help_section, help_usage, show, show_if_err};
|
||||
|
||||
static DEFAULT_PERM: u32 = 0o755;
|
||||
|
||||
const ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
|
||||
const USAGE: &str = "{} [OPTION]... [USER]";
|
||||
const LONG_USAGE: &str =
|
||||
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.";
|
||||
const ABOUT: &str = help_about!("mkdir.md");
|
||||
const USAGE: &str = help_usage!("mkdir.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "mkdir.md");
|
||||
|
||||
mod options {
|
||||
pub const MODE: &str = "mode";
|
||||
|
@ -90,7 +89,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
// Linux-specific options, not implemented
|
||||
// opts.optflag("Z", "context", "set SELinux security context" +
|
||||
// " of each created directory to CTX"),
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?;
|
||||
|
||||
let dirs = matches
|
||||
.get_many::<OsString>(options::DIRS)
|
||||
|
|
|
@ -18,7 +18,7 @@ path = "src/more.rs"
|
|||
clap = { workspace=true }
|
||||
uucore = { workspace=true }
|
||||
crossterm = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace=true }
|
||||
unicode-width = { workspace=true }
|
||||
unicode-segmentation = { workspace=true }
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ use crossterm::{
|
|||
terminal,
|
||||
};
|
||||
|
||||
use is_terminal::IsTerminal;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use uucore::display::Quotable;
|
||||
|
@ -83,7 +84,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
buff.clear();
|
||||
}
|
||||
reset_term(&mut stdout);
|
||||
} else if atty::isnt(atty::Stream::Stdin) {
|
||||
} else if !std::io::stdin().is_terminal() {
|
||||
stdin().read_to_string(&mut buff).unwrap();
|
||||
let mut stdout = setup_term();
|
||||
more(&buff, &mut stdout, None, silent)?;
|
||||
|
|
|
@ -17,7 +17,7 @@ path = "src/nohup.rs"
|
|||
[dependencies]
|
||||
clap = { workspace=true }
|
||||
libc = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace=true }
|
||||
uucore = { workspace=true, features=["fs"] }
|
||||
|
||||
[[bin]]
|
||||
|
|
15
src/uu/nohup/nohup.md
Normal file
15
src/uu/nohup/nohup.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# nohup
|
||||
|
||||
```
|
||||
nohup COMMAND [ARG]...
|
||||
nohup FLAG
|
||||
```
|
||||
|
||||
Run COMMAND ignoring hangup signals.
|
||||
|
||||
## After Help
|
||||
|
||||
If standard input is terminal, it'll be replaced with /dev/null.
|
||||
If standard output is terminal, it'll be appended to nohup.out instead,
|
||||
or $HOME/nohup.out, if nohup.out open failed.
|
||||
If standard error is terminal, it'll be redirected to stdout.
|
|
@ -8,6 +8,7 @@
|
|||
// spell-checker:ignore (ToDO) execvp SIGHUP cproc vprocmgr cstrs homeout
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use is_terminal::IsTerminal;
|
||||
use libc::{c_char, dup2, execvp, signal};
|
||||
use libc::{SIGHUP, SIG_IGN};
|
||||
use std::env;
|
||||
|
@ -19,18 +20,11 @@ use std::os::unix::prelude::*;
|
|||
use std::path::{Path, PathBuf};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{set_exit_code, UClapError, UError, UResult};
|
||||
use uucore::{format_usage, show_error};
|
||||
use uucore::{format_usage, help_about, help_section, help_usage, show_error};
|
||||
|
||||
static ABOUT: &str = "Run COMMAND ignoring hangup signals.";
|
||||
static LONG_HELP: &str = "
|
||||
If standard input is terminal, it'll be replaced with /dev/null.
|
||||
If standard output is terminal, it'll be appended to nohup.out instead,
|
||||
or $HOME/nohup.out, if nohup.out open failed.
|
||||
If standard error is terminal, it'll be redirected to stdout.
|
||||
";
|
||||
const USAGE: &str = "\
|
||||
{} COMMAND [ARG]...
|
||||
{} FLAG";
|
||||
const ABOUT: &str = help_about!("nohup.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "nohup.md");
|
||||
const USAGE: &str = help_usage!("nohup.md");
|
||||
static NOHUP_OUT: &str = "nohup.out";
|
||||
// exit codes that match the GNU implementation
|
||||
static EXIT_CANCELED: i32 = 125;
|
||||
|
@ -115,7 +109,7 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.arg(
|
||||
Arg::new(options::CMD)
|
||||
|
@ -129,7 +123,7 @@ pub fn uu_app() -> Command {
|
|||
}
|
||||
|
||||
fn replace_fds() -> UResult<()> {
|
||||
if atty::is(atty::Stream::Stdin) {
|
||||
if std::io::stdin().is_terminal() {
|
||||
let new_stdin = File::open(Path::new("/dev/null"))
|
||||
.map_err(|e| NohupError::CannotReplace("STDIN", e))?;
|
||||
if unsafe { dup2(new_stdin.as_raw_fd(), 0) } != 0 {
|
||||
|
@ -137,7 +131,7 @@ fn replace_fds() -> UResult<()> {
|
|||
}
|
||||
}
|
||||
|
||||
if atty::is(atty::Stream::Stdout) {
|
||||
if std::io::stdout().is_terminal() {
|
||||
let new_stdout = find_stdout()?;
|
||||
let fd = new_stdout.as_raw_fd();
|
||||
|
||||
|
@ -146,7 +140,7 @@ fn replace_fds() -> UResult<()> {
|
|||
}
|
||||
}
|
||||
|
||||
if atty::is(atty::Stream::Stderr) && unsafe { dup2(1, 2) } != 2 {
|
||||
if std::io::stderr().is_terminal() && unsafe { dup2(1, 2) } != 2 {
|
||||
return Err(NohupError::CannotReplace("STDERR", Error::last_os_error()).into());
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -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
|
||||
|
|
49
src/uu/od/od.md
Normal file
49
src/uu/od/od.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
# od
|
||||
|
||||
```
|
||||
od [OPTION]... [--] [FILENAME]...
|
||||
od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]]
|
||||
od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]
|
||||
```
|
||||
|
||||
Dump files in octal and other formats
|
||||
|
||||
## After Help
|
||||
|
||||
Displays data in various human-readable formats. If multiple formats are
|
||||
specified, the output will contain all formats in the order they appear on the
|
||||
command line. Each format will be printed on a new line. Only the line
|
||||
containing the first format will be prefixed with the offset.
|
||||
|
||||
If no filename is specified, or it is "-", stdin will be used. After a "--", no
|
||||
more options will be recognized. This allows for filenames starting with a "-".
|
||||
|
||||
If a filename is a valid number which can be used as an offset in the second
|
||||
form, you can force it to be recognized as a filename if you include an option
|
||||
like "-j0", which is only valid in the first form.
|
||||
|
||||
RADIX is one of o,d,x,n for octal, decimal, hexadecimal or none.
|
||||
|
||||
BYTES is decimal by default, octal if prefixed with a "0", or hexadecimal if
|
||||
prefixed with "0x". The suffixes b, KB, K, MB, M, GB, G, will multiply the
|
||||
number with 512, 1000, 1024, 1000^2, 1024^2, 1000^3, 1024^3, 1000^2, 1024^2.
|
||||
|
||||
OFFSET and LABEL are octal by default, hexadecimal if prefixed with "0x" or
|
||||
decimal if a "." suffix is added. The "b" suffix will multiply with 512.
|
||||
|
||||
TYPE contains one or more format specifications consisting of:
|
||||
a for printable 7-bits ASCII
|
||||
c for utf-8 characters or octal for undefined characters
|
||||
d[SIZE] for signed decimal
|
||||
f[SIZE] for floating point
|
||||
o[SIZE] for octal
|
||||
u[SIZE] for unsigned decimal
|
||||
x[SIZE] for hexadecimal
|
||||
SIZE is the number of bytes which can be the number 1, 2, 4, 8 or 16,
|
||||
or C, I, S, L for 1, 2, 4, 8 bytes for integer types,
|
||||
or F, D, L for 4, 8, 16 bytes for floating point.
|
||||
Any type specification can have a "z" suffix, which will add a ASCII dump at
|
||||
the end of the line.
|
||||
|
||||
If an error occurred, a diagnostic message will be printed to stderr, and the
|
||||
exit code will be non-zero.
|
|
@ -44,57 +44,15 @@ use clap::ArgAction;
|
|||
use clap::{crate_version, parser::ValueSource, Arg, ArgMatches, Command};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::format_usage;
|
||||
use uucore::parse_size::ParseSizeError;
|
||||
use uucore::show_error;
|
||||
use uucore::show_warning;
|
||||
use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_warning};
|
||||
|
||||
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes
|
||||
static ABOUT: &str = "Dump files in octal and other formats";
|
||||
const ABOUT: &str = help_about!("od.md");
|
||||
|
||||
static USAGE: &str = "\
|
||||
{} [OPTION]... [--] [FILENAME]...
|
||||
{} [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]]
|
||||
{} --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]";
|
||||
const USAGE: &str = help_usage!("od.md");
|
||||
|
||||
static LONG_HELP: &str = r#"
|
||||
Displays data in various human-readable formats. If multiple formats are
|
||||
specified, the output will contain all formats in the order they appear on the
|
||||
command line. Each format will be printed on a new line. Only the line
|
||||
containing the first format will be prefixed with the offset.
|
||||
|
||||
If no filename is specified, or it is "-", stdin will be used. After a "--", no
|
||||
more options will be recognized. This allows for filenames starting with a "-".
|
||||
|
||||
If a filename is a valid number which can be used as an offset in the second
|
||||
form, you can force it to be recognized as a filename if you include an option
|
||||
like "-j0", which is only valid in the first form.
|
||||
|
||||
RADIX is one of o,d,x,n for octal, decimal, hexadecimal or none.
|
||||
|
||||
BYTES is decimal by default, octal if prefixed with a "0", or hexadecimal if
|
||||
prefixed with "0x". The suffixes b, KB, K, MB, M, GB, G, will multiply the
|
||||
number with 512, 1000, 1024, 1000^2, 1024^2, 1000^3, 1024^3, 1000^2, 1024^2.
|
||||
|
||||
OFFSET and LABEL are octal by default, hexadecimal if prefixed with "0x" or
|
||||
decimal if a "." suffix is added. The "b" suffix will multiply with 512.
|
||||
|
||||
TYPE contains one or more format specifications consisting of:
|
||||
a for printable 7-bits ASCII
|
||||
c for utf-8 characters or octal for undefined characters
|
||||
d[SIZE] for signed decimal
|
||||
f[SIZE] for floating point
|
||||
o[SIZE] for octal
|
||||
u[SIZE] for unsigned decimal
|
||||
x[SIZE] for hexadecimal
|
||||
SIZE is the number of bytes which can be the number 1, 2, 4, 8 or 16,
|
||||
or C, I, S, L for 1, 2, 4, 8 bytes for integer types,
|
||||
or F, D, L for 4, 8, 16 bytes for floating point.
|
||||
Any type specification can have a "z" suffix, which will add a ASCII dump at
|
||||
the end of the line.
|
||||
|
||||
If an error occurred, a diagnostic message will be printed to stderr, and the
|
||||
exitcode will be non-zero."#;
|
||||
const AFTER_HELP: &str = help_section!("after help", "od.md");
|
||||
|
||||
pub(crate) mod options {
|
||||
pub const HELP: &str = "help";
|
||||
|
@ -295,7 +253,7 @@ pub fn uu_app() -> Command {
|
|||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.trailing_var_arg(true)
|
||||
.dont_delimit_trailing_values(true)
|
||||
.infer_long_args(true)
|
||||
|
|
7
src/uu/pwd/pwd.md
Normal file
7
src/uu/pwd/pwd.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# pwd
|
||||
|
||||
```
|
||||
pwd [OPTION]... [FILE]...
|
||||
```
|
||||
|
||||
Display the full filename of the current working directory.
|
|
@ -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";
|
||||
|
||||
|
|
7
src/uu/realpath/realpath.md
Normal file
7
src/uu/realpath/realpath.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# realpath
|
||||
|
||||
```
|
||||
realpath [OPTION]... FILE...
|
||||
```
|
||||
|
||||
Print the resolved path
|
|
@ -20,11 +20,12 @@ use uucore::{
|
|||
error::{FromIo, UResult},
|
||||
format_usage,
|
||||
fs::{canonicalize, MissingHandling, ResolveMode},
|
||||
help_about, help_usage,
|
||||
};
|
||||
use uucore::{error::UClapError, show, show_if_err};
|
||||
|
||||
static ABOUT: &str = "Print the resolved path";
|
||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
||||
static ABOUT: &str = help_about!("realpath.md");
|
||||
const USAGE: &str = help_usage!("realpath.md");
|
||||
|
||||
static OPT_QUIET: &str = "quiet";
|
||||
static OPT_STRIP: &str = "strip";
|
||||
|
|
22
src/uu/rm/rm.md
Normal file
22
src/uu/rm/rm.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# rm
|
||||
|
||||
```
|
||||
rm [OPTION]... FILE...
|
||||
```
|
||||
|
||||
Remove (unlink) the FILE(s)
|
||||
|
||||
## After Help
|
||||
|
||||
By default, rm does not remove directories. Use the --recursive (-r or -R)
|
||||
option to remove each listed directory, too, along with all of its contents
|
||||
|
||||
To remove a file whose name starts with a '-', for example '-foo',
|
||||
use one of these commands:
|
||||
rm -- -foo
|
||||
|
||||
rm ./-foo
|
||||
|
||||
Note that if you use rm to remove a file, it might be possible to recover
|
||||
some of its contents, given sufficient expertise and/or time. For greater
|
||||
assurance that the contents are truly unrecoverable, consider using shred.
|
|
@ -15,7 +15,7 @@ use std::ops::BitOr;
|
|||
use std::path::{Path, PathBuf};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||
use uucore::{format_usage, prompt_yes, show_error};
|
||||
use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show_error};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Copy)]
|
||||
|
@ -37,21 +37,9 @@ struct Options {
|
|||
verbose: bool,
|
||||
}
|
||||
|
||||
const ABOUT: &str = "Remove (unlink) the FILE(s)";
|
||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
||||
const LONG_USAGE: &str = "\
|
||||
By default, rm does not remove directories. Use the --recursive (-r or -R)
|
||||
option to remove each listed directory, too, along with all of its contents
|
||||
|
||||
To remove a file whose name starts with a '-', for example '-foo',
|
||||
use one of these commands:
|
||||
rm -- -foo
|
||||
|
||||
rm ./-foo
|
||||
|
||||
Note that if you use rm to remove a file, it might be possible to recover
|
||||
some of its contents, given sufficient expertise and/or time. For greater
|
||||
assurance that the contents are truly unrecoverable, consider using shred.";
|
||||
const ABOUT: &str = help_about!("rm.md");
|
||||
const USAGE: &str = help_usage!("rm.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "rm.md");
|
||||
|
||||
static OPT_DIR: &str = "dir";
|
||||
static OPT_INTERACTIVE: &str = "interactive";
|
||||
|
@ -69,7 +57,7 @@ static ARG_FILES: &str = "files";
|
|||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?;
|
||||
|
||||
let files: Vec<String> = matches
|
||||
.get_many::<String>(ARG_FILES)
|
||||
|
|
7
src/uu/rmdir/rmdir.md
Normal file
7
src/uu/rmdir/rmdir.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# rmdir
|
||||
|
||||
```
|
||||
rmdir [OPTION]... DIRECTORY...
|
||||
```
|
||||
|
||||
Remove the DIRECTORY(ies), if they are empty.
|
|
@ -16,10 +16,10 @@ use std::path::Path;
|
|||
use uucore::display::Quotable;
|
||||
use uucore::error::{set_exit_code, strip_errno, UResult};
|
||||
|
||||
use uucore::{format_usage, show_error, util_name};
|
||||
use uucore::{format_usage, help_about, help_usage, show_error, util_name};
|
||||
|
||||
static ABOUT: &str = "Remove the DIRECTORY(ies), if they are empty.";
|
||||
const USAGE: &str = "{} [OPTION]... DIRECTORY...";
|
||||
static ABOUT: &str = help_about!("rmdir.md");
|
||||
const USAGE: &str = help_usage!("rmdir.md");
|
||||
static OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty";
|
||||
static OPT_PARENTS: &str = "parents";
|
||||
static OPT_VERBOSE: &str = "verbose";
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue