Skip to content

(#9400 P2b) Replace echo|pipe subshells with here-strings/parameter expansion#9947

Open
iav wants to merge 11 commits into
armbian:mainfrom
iav:fix/p2b-echo-pipe-subshells
Open

(#9400 P2b) Replace echo|pipe subshells with here-strings/parameter expansion#9947
iav wants to merge 11 commits into
armbian:mainfrom
iav:fix/p2b-echo-pipe-subshells

Conversation

@iav

@iav iav commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the last open group from #9400 (the Bash syntax safety cleanup) — P2b:
seventeen echo "$var" | grep/sed/cut/awk/sha256sum … chains across seven
files, replaced with bash-native here-strings or parameter expansions. Each
chain forked a subshell and one or two external processes just to do
something bash can do in-place; behavior is preserved verbatim.

GitHub issue reference: #9400

Changes

File Sites Before After
lib/functions/compilation/uboot.sh 1 echo $root_partition | sed 's/\/dev\///g' "${root_partition#/dev/}"
lib/functions/general/python-tools.sh 1 $(echo … | awk '{print $2}' | cut -d. -f1,2) two parameter expansions (${var#* } then ${_%.*})
lib/functions/image/partitioning.sh 1 $(echo "$ver" | awk -F. …) IFS=. read + printf -v (with non-numeric suffix strip so prerelease util-linux builds don't bomb)
lib/functions/general/git.sh 1 $(echo "$url" | sed "s|^https://github.com/|…") guarded prefix trim ([[ "${url}" == https://github.com/* ]] + ${url#…})
lib/functions/general/git-ref2info.sh 3 $(echo "${git_source}" | cut -d/ -f4-5) (×3) IFS=/ read -r _ _ _ org repo _
lib/functions/general/hash-files.sh 3 $(echo "${x}" | sha256sum | cut -d' ' -f1) (×3) $(sha256sum <<< "${x}" | cut -d' ' -f1)
lib/functions/host/docker.sh 7 $(echo "${DOCKER_INFO}" | grep -i -e "K:" | cut -d : -f 2 | xargs echo -n) (×7) $(grep -i -e "K:" <<< "${DOCKER_INFO}" | …)

Why this matters

Each removed echo \| … chain saves one subshell and one external process
per call. get_docker_info_once alone runs seven of these in a tight
sequence at the top of every Docker-mode build; hash-files ones run on
every artifact hash computation. The overall wall-clock benefit is modest,
but the diff value is largely about readability and not pretending shell
needs awk/sed/echo for trivial string surgery.

Note on hash-files.sh (was excluded in #9400, now included)

The original #9400 P2b excluded echo "${x}" | sha256sum, on the assumption
that the <<< here-string alternative would append a different trailing
newline and so change the hash. Empirically that is not the case: both
echo "$x" | sha256sum and sha256sum <<< "$x" emit one trailing \n
and produce identical hashes — only printf '%s' "$x" | sha256sum (no
newline) differs. The exclusion has been removed from #9400 accordingly,
and the three hash-files.sh sites are converted here.

Preparatory style commits

Three style(shellfmt): … commits are carried in this PR ahead of the
functional changes they touch. They are pure shellfmt runs over the same
file: re-indented python_proxy_env array elements, collapsed comment
alignment in a git.sh submodule loop, and a tree-wide format of
docker.sh (which had drifted significantly from shellfmt's rules). They
are split out so each P2b commit is purely functional.

Out of scope (deliberately)

Test plan

  • bash lib/tools/shellcheck.sh over the whole tree — clean.
  • Hash-equivalence of echo "$x" | sha256sum vs sha256sum <<< "$x"
    verified empirically with three sample inputs.
  • Docker info field parsing verified equivalent old vs new pattern.
  • Smoke compile.sh Docker build (to exercise the docker.sh
    get_docker_info_once field reads).

Summary by CodeRabbit

  • Refactor
    • Replaced fragile shell text-processing pipelines with safer parameter expansion and direct input hashing to improve reliability.
    • Improved repository URL parsing for common hosting services to ensure correct org/repo derivation.
    • Enhanced Docker inspection parsing (versions, resources, host identity) and normalization handling to reduce edge-case misdetections.
    • Improved boot partition root-device name handling and made partitioning decisions more robust against prerelease/RC sfdisk version strings.

@iav iav requested a review from a team as a code owner June 7, 2026 15:49
@iav iav requested review from lanefu and sgjava and removed request for a team June 7, 2026 15:49
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dd0ded0c-0146-45cc-ad21-b5b5761b8f80

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Refactors multiple Armbian build library scripts to replace pipeline-based text processing with bash-native constructs: parameter expansion, IFS-based parsing, and here-strings. Preserves existing behavior while tightening hostname matching patterns and refactoring heredoc construction into command-substitution wrappers.

Changes

Unified bash pattern modernization

Layer / File(s) Summary
String manipulation via parameter expansion
lib/functions/compilation/uboot.sh, lib/functions/general/git.sh
Device root name and submodule name trimming use bash parameter expansion (${root_partition#/dev/}, ${key#submodule.} / ${name%.path}) instead of sed/cut pipelines.
Path and URL field extraction with IFS and pattern matching
lib/functions/general/git-ref2info.sh, lib/functions/general/git.sh
Org/repo extraction for gitverse.ru, gitee.com, and github.com uses IFS=/ read -r segment capture and .git suffix removal; GitHub URL rewriting uses bash pattern matching/parameter expansion instead of sed.
Hash computation via here-strings
lib/functions/general/hash-files.sh
Secondary hashes in calculate_hash_for_files, calculate_hash_for_function_bodies, and calculate_hash_for_variables computed via sha256sum <<< ... here-strings and truncated to 16 characters, replacing `echo
Version string parsing with bash-native splitting
lib/functions/general/python-tools.sh, lib/functions/image/partitioning.sh
Python major.minor and sfdisk numeric version conversion use bash dot-splitting and parameter expansion with printf formatting, handling prerelease suffixes instead of awk/cut pipelines.
Environment array restructuring
lib/functions/general/python-tools.sh
python_proxy_env array elements emitted as separate assignments, preserving lowercase/uppercase proxy variable fallbacks and APT_PROXY_ADDR logic.
Docker information field extraction with here-strings
lib/functions/host/docker.sh
Docker server/kernel/RAM/CPU/OS/buildx fields extracted with grep ... <<< "${DOCKER_INFO}" here-strings; server hostname matching tightened to anchored ^ *Name: line selection.
Docker script formatting and heredoc refactoring
lib/functions/host/docker.sh
Whitespace/blank-line normalization in get_docker_info_once(), minor loop/header spacing adjustments, DNS nameserver loop terminator reformatting, and wrapper/cron content heredocs refactored to $(cat <<-'EOT' ...) command-substitution form with preserved placeholders and installation logic.

Sequence Diagram(s)

(No sequence diagrams generated: changes are primarily refactoring existing patterns with preserved behavior rather than introducing new multi-component interactions or control flow modifications.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Suggested labels

05, size/small

Suggested reviewers

  • hzyitc
  • TRSx80

Poem

🐰 Pipes trimmed down, now sleek and light,
Bash sniffs the strings and trims them right.
Here-strings hum, IFS reads along,
Sed and cut nap while scripts stay strong.
A small hop for code, a tidy delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: replacing echo|pipe patterns with bash-native alternatives (here-strings and parameter expansion).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added 08 Milestone: Third quarter release size/medium PR with more then 50 and less then 250 lines Needs review Seeking for review Framework Framework components labels Jun 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/functions/host/docker.sh`:
- Line 213: The DOCKER_SERVER_NAME_HOST assignment is matching any "name:" token
(including "Username:"), so update the extraction to anchor to the literal
"Name:" field in DOCKER_INFO; modify the DOCKER_SERVER_NAME_HOST line that reads
from DOCKER_INFO to grep only lines that start (optionally after whitespace)
with "Name:" (case-insensitive if desired), then continue to cut -d ":" -f2 |
xargs echo -n as before so the Darwin case dispatch uses the correct host value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 32a3c7a6-c7d8-4680-936b-8f9f8b7da937

📥 Commits

Reviewing files that changed from the base of the PR and between 4235718 and c414b0b.

📒 Files selected for processing (7)
  • lib/functions/compilation/uboot.sh
  • lib/functions/general/git-ref2info.sh
  • lib/functions/general/git.sh
  • lib/functions/general/hash-files.sh
  • lib/functions/general/python-tools.sh
  • lib/functions/host/docker.sh
  • lib/functions/image/partitioning.sh

Comment thread lib/functions/host/docker.sh Outdated
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

iav added 8 commits June 14, 2026 23:24
… parameter expansion

Replace `echo $root_partition | sed 's/\/dev\///g'` with the bash-native
`${root_partition#/dev/}` prefix-trim. Removes a subshell plus two external
processes (echo, sed) on every u-boot update path.

The variable `root_partition_name` is assigned but unused both before and
after this change; cleaning that up is out of scope for P2b.

Part of armbian#9400 (P2b).

Assisted-by: Claude:claude-opus-4.7
Re-indent the `python_proxy_env` array elements with one extra tab so the
file no longer drifts when shellfmt is run scoped on it. No behavior change.

Pure preparation for the following P2b commit; carried in the same PR so
the functional change diff is not contaminated with formatting noise.

Assisted-by: Claude:claude-opus-4.7
…ll with parameter expansion

Replace
  $(echo "${python3_version_full}" | awk '{print $2}' | cut -d. -f1,2)
with two bash-native parameter expansions: `${python3_version_full#* }`
strips the "Python " prefix, then `${_py_ver_triplet%.*}` drops the patch
component. Same observable output (e.g. "3.12"), one subshell and three
external processes (echo, awk, cut) eliminated.

Part of armbian#9400 (P2b).

Assisted-by: Claude:claude-opus-4.7
…h parameter expansion

Split the sfdisk version (e.g. "2.41.1") directly with `IFS=. read` and
format the numeric form (e.g. "024101") with `printf -v`, eliminating
the `echo | awk` subshell. Same observable output.

Each component is then stripped of any non-numeric suffix (e.g.
"2.41-rc1" -> minor "41", not "41-rc1") before the printf so we don't
bomb under `set -e` on prerelease util-linux builds; the prior
`awk -F. '{printf "%d%02d%02d"}'` quietly accepted the numeric prefix,
and we keep that tolerance here.

Spotted by @chatgpt-codex-connector on PR #161 (iav/armbian fork pre-review).

Part of armbian#9400 (P2b).

Assisted-by: Claude:claude-opus-4.7
shellfmt collapses the extra spaces before the inline `# -> libfoo` and
`# -> libfoo.path` comments to its single-space rule. No behavior change.

Pure preparation for the following P2b commit; carried in the same PR so
the functional change diff is not contaminated with formatting noise.

Assisted-by: Claude:claude-opus-4.7
…x-trim

Replace the `echo "$url" | sed "s|^https://github.com/|${GITHUB_SOURCE}/|"`
form with a guarded bash-native prefix trim. The guard preserves the
sed-with-^ semantics: only URLs literally starting with the GitHub host are
rewritten; anything else (private mirrors, gitlab, etc.) is left alone.
Eliminates one subshell + the sed external on every fetch_from_repo call.

Part of armbian#9400 (P2b).

Assisted-by: Claude:claude-opus-4.7
…ith IFS=/ read

Replace three identical `$(echo "${git_source}" | cut -d/ -f4-5)` subshells
(in the gitverse.ru, gitee.com and github.com mirror branches) with bash-
native `IFS=/ read -r _ _ _ _gr_org _gr_repo _ <<< "${git_source}"`. Same
output: skip three leading components (scheme, empty, host), capture org
and repo, drop the rest.

Eliminates three echo+cut subshells per ref-resolution call.

Part of armbian#9400 (P2b).

Assisted-by: Claude:claude-opus-4.7
…lls with here-strings

Replace three `echo "${var}" | sha256sum | cut -d' ' -f1` forms with
`sha256sum <<< "${var}" | cut -d' ' -f1`. Verified empirically that both
forms emit identical bytes (one trailing newline in both cases, same hash);
the form-equivalence is also the reason this group was originally excluded
from the armbian#9400 P2b list — that exclusion has now been removed from armbian#9400
because the trailing-newline concern does not apply (echo without -n and
the here-string redirection both append a single \n).

Eliminates one subshell + one external `echo` invocation per call.

For `function_bodies` the in-array `[@]` splat is swapped for `[*]` so
the array is joined into a single here-string token (IFS default = space).
Empirically equivalent to the prior `echo "${arr[@]}"` output.

Part of armbian#9400 (P2b).

Assisted-by: Claude:claude-opus-4.7
@iav iav force-pushed the fix/p2b-echo-pipe-subshells branch from 127c794 to 6000c81 Compare June 14, 2026 20:25
@iav iav marked this pull request as draft June 14, 2026 20:38
iav added 3 commits June 15, 2026 00:20
Apply shellfmt's space-after-redirect (`2> /dev/null`), C-style for-loop
operator spacing (`((i = 2; i < ...))`), and collapse a stray double blank
line.

Heredoc bodies (the `cat <<- 'EOT'` wrapper/cron scripts) are left untouched
on purpose: shfmt re-indents them, which survives only because `<<-` strips
leading tabs; it bloats the diff for no functional gain, so it is kept out of
scope.

No behavior change.

Assisted-by: Claude:claude-opus-4.8
…th here-strings

Seven near-identical `echo "${DOCKER_INFO}" | grep -i -e <key>: | cut -d : -f 2 | xargs echo -n`
chains parse fields out of cached `docker info` output. Replace each with
`grep -i -e <key>: <<< "${DOCKER_INFO}" | cut -d : -f 2 | xargs echo -n`.

The here-string and `echo "${DOCKER_INFO}"` produce the same bytes on
stdin (one trailing newline), so grep, cut and xargs see identical input
and emit the same field value. The change drops one subshell + one echo
process per invocation; `get_docker_info_once` runs these in a tight
sequence at the top of every Docker-mode build.

Part of armbian#9400 (P2b).

Assisted-by: Claude:claude-opus-4.7
`docker info` emits both `Username: <user>` (under `Server:`) and
`Name: <host>` near the bottom; the previous grep pattern `-i -e name:`
caught both, so on hosts where docker stores credentials we ended up with
`DOCKER_SERVER_NAME_HOST="<user> <host>"` after `cut + xargs` glued the
two matches together. The Darwin `case` dispatch downstream then failed
to recognise "Docker Desktop" / "Rancher Desktop", silently turning off
the loop-hack workarounds.

Anchor the grep to a leading-whitespace + literal `Name:` and take only
the first match (`-m1`). Pre-existing on main, surfaced during P2b review
by @coderabbitai.

Assisted-by: Claude:claude-opus-4.7
@iav iav force-pushed the fix/p2b-echo-pipe-subshells branch from 6000c81 to 649c954 Compare June 14, 2026 21:21
@iav iav marked this pull request as ready for review June 14, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

08 Milestone: Third quarter release Framework Framework components Needs review Seeking for review size/medium PR with more then 50 and less then 250 lines

Development

Successfully merging this pull request may close these issues.

1 participant