TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

Techniques I use to create a great user experience for shell scripts

412 点作者 hundredwatt8 个月前

38 条评论

haileys8 个月前
Don&#x27;t output ANSI colour codes directly - your output could redirect to a file, or perhaps the user simply prefers no colour. Use tput instead, and add a little snippet like this to the top of your script:<p><pre><code> command -v tput &amp;&gt;&#x2F;dev&#x2F;null &amp;&amp; [ -t 1 ] &amp;&amp; [ -z &quot;${NO_COLOR:-}&quot; ] || tput() { true; } </code></pre> This checks that the tput command exists (using the bash &#x27;command&#x27; builtin rather than which(1) - surprisingly, which can&#x27;t always be relied upon to be installed even on modern GNU&#x2F;Linux systems), that stdout is a tty, and that the NO_COLOR env var is not set. If any of these conditions are false, a no-op tput function is defined.<p>This little snippet of setup lets you sprinkle tput invocations through your script knowing that it&#x27;s going to do the right thing in any situation.
评论 #41537529 未加载
评论 #41536965 未加载
评论 #41537251 未加载
评论 #41536210 未加载
评论 #41538964 未加载
评论 #41540064 未加载
Arch-TK8 个月前
This reads like what I&#x27;ve named as &quot;consultantware&quot; which is a type of software developed by security consultants who are eager to write helpful utilities but have no idea about the standards for how command line software behaves on Linux.<p>It ticks so many boxes:<p>* Printing non-output information to stdout (usage information is not normal program output, use stderr instead)<p>* Using copious amounts of colours everywhere to draw attention to error messages.<p>* ... Because you&#x27;ve flooded my screen with even larger amount of irrelevant noise which I don&#x27;t care about (what is being ran).<p>* Coming up with a completely custom and never before seen way of describing the necessary options and arguments for a program.<p>* Trying to auto-detect the operating system instead of just documenting the non-standard dependencies and providing a way to override them (inevitably extremely fragile and makes the end-user experience worse). If you are going to implement automatic fallbacks, at least provide a warning to the end user.<p>* ... All because you&#x27;ve tried to implement a &quot;helpful&quot; (but unnecessary) feature of a timeout which the person using your script could have handled themselves instead.<p>* pipefail when nothing is being piped (pipefail is not a &quot;fix&quot; it is an option, whether it is appropriate is dependant on the pipeline, it&#x27;s not something you should be blanket applying to your codebase)<p>* Spamming output in the current directory without me specifying where you should put it or expecting it to even happen.<p>* Using set -e without understanding how it works (and where it doesn&#x27;t work).
评论 #41539181 未加载
评论 #41539381 未加载
评论 #41540568 未加载
评论 #41543086 未加载
评论 #41539926 未加载
评论 #41542071 未加载
评论 #41539641 未加载
sgarland8 个月前
Nowhere in this list did I see “use shellcheck.”<p>On the scale of care, “the script can blow up in surprising ways” severely outweighs “error messages are in red.” Also, as someone else pointed out, what if I’m redirecting to a file?
评论 #41536909 未加载
评论 #41537661 未加载
评论 #41539266 未加载
评论 #41538550 未加载
评论 #41539679 未加载
gorgoiler8 个月前
It is impossible to write a safe shell script that does automatic error checking while using the features the language claims are available to you.<p>Here’s a script that uses <i>real language</i> things like a function and error checking, but which also prints “oh no”:<p><pre><code> set -e f() { false echo oh } if f then echo no fi </code></pre> <i>set -e</i> is off when your function is called as a predicate. That’s such a letdown from expected- to actual-behavior that I threw it in the bin as a programming language. The only remedy is for each function to be its own script. Great!<p>In terms of sh enlightenment, one of the steps before getting to the above is realizing that every time you use “;” you are using a technique to jam a multi-line expression onto a single line. It starts to feel incongruous to mix single line and multi line syntax:<p><pre><code> # weird if foo; then bar fi # ahah if foo then bar fi </code></pre> Writing long scripts without semicolons felt refreshing, like I was using the syntax in the way that nature intended.<p>Shell scripting has its place. Command invocation with sh along with C functions is the de-facto API in Linux. Shell scripts need to fail fast and hard though and leave it up to the caller (either a different language, or another shell script) to figure out how to handle errors.
Yasuraka8 个月前
Here&#x27;s a script that left an impression on me the first time I saw it:<p><a href="https:&#x2F;&#x2F;github.com&#x2F;containerd&#x2F;nerdctl&#x2F;blob&#x2F;main&#x2F;extras&#x2F;rootless&#x2F;containerd-rootless-setuptool.sh">https:&#x2F;&#x2F;github.com&#x2F;containerd&#x2F;nerdctl&#x2F;blob&#x2F;main&#x2F;extras&#x2F;rootl...</a><p>I have since copied this pattern for many scripts: logging functions, grouping all global vars and constants at the top and creating subcommands using shift.
评论 #41538305 未加载
koolba8 个月前
<p><pre><code> if [ &quot;$(uname -s)&quot; == &quot;Linux” ]; then stuff-goes-here else # Assume MacOS </code></pre> While probably true for most folks, that’s hardly what I’d call great for everybody not on Linux or a Mac.
评论 #41536183 未加载
评论 #41539306 未加载
评论 #41541363 未加载
评论 #41536227 未加载
archargelod8 个月前
One of my favorite techniques for shell scripts, not mentioned in the article:<p>For rarely run scripts, consider checking if required flags are missing and query for user input, for example:<p><pre><code> [[ -z &quot;$filename&quot; ]] &amp;&amp; printf &quot;Enter filename to edit: &quot; &amp;&amp; read filename </code></pre> Power users already know to always do `-h &#x2F; --help` first, but this way even people that are less familiar with command line can use your tool.<p>if that&#x27;s a script that&#x27;s run very rarely or once, entering the fields sequentially could also save time, compared to common `try to remember flags -&gt; error -&gt; check help -&gt; success` flow.
xyzzy47478 个月前
Not trying to offend anyone here but I think shell scripts are the wrong solution for anything over ~50 lines of code.<p>Use a better programming language. Go, Typescript, Rust, Python, and even Perl come to mind.
评论 #41536552 未加载
评论 #41537214 未加载
评论 #41536125 未加载
评论 #41541440 未加载
评论 #41537794 未加载
评论 #41539708 未加载
评论 #41536237 未加载
0xbadcafebee8 个月前
If you want a great script user experience, I highly recommend avoiding the use of pipefail. It causes your script to die unexpectedly with no output. You can add traps and error handlers and try to dig out of PIPESTATUS the offending failed intermediate pipe just to tell the user why the program is exiting unexpectedly, but you can&#x27;t resume code execution from where the exception happened. You&#x27;re also now writing a complicated ass program that should probably be in a more complete language.<p>Instead, just check $? and whether a pipe&#x27;s output has returned anything at all (<i>[ -z &quot;$FOO&quot; ]</i>) or if it looks similar to what you expect. This is good enough for 99% of scripts and allows you to fail gracefully or even just keep going despite the error (which is good enough for 99.99% of cases). You can also still check intermediate pipe return status from PIPESTATUS and handle those errors gracefully too.
评论 #41541467 未加载
TeeMassive8 个月前
I&#x27;d add, in each my Bash scripts I add this line to get the script&#x27;s current directory:<p>SCRIPT_DIR=$( cd -- &quot;$( dirname -- &quot;${BASH_SOURCE[0]}&quot; )&quot; &amp;&gt; &#x2F;dev&#x2F;null &amp;&amp; pwd )<p>This is based on this SA&#x27;s answer: <a href="https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;59895&#x2F;how-do-i-get-the-directory-where-a-bash-script-is-located-from-within-the-script" rel="nofollow">https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;59895&#x2F;how-do-i-get-the-d...</a><p>I never got why Bash doesn&#x27;t have a reliable &quot;this file&#x27;s path&quot; feature and why people always take the current working directory for granted!
评论 #41538306 未加载
评论 #41537584 未加载
jiggawatts8 个月前
Every time I see a “good” bash script it reminds me of how incredibly primitive every shell is other than PowerShell.<p>Validating parameters - a built in declarative feature! E.g.: ValidateNotNullOrEmpty.<p>Showing progress — also built in, and <i>doesn’t</i> pollute the output stream so you can process returned text AND see progress at the same time. (Write-Progress)<p>Error handling — Try { } Catch { } Finally { } works just like with proper programming languages.<p>Platform specific — PowerShell doesn’t rely on a huge collection of non-standard CLI tools for essential functionality. It has built-in portable commands for sorting, filtering, format conversions, and many more. Works the same on Linux and Windows.<p>Etc…<p>PS: Another super power that bash users aren’t even aware they’re missing out on is that PowerShell can be embedded into a process as a library (not an external process!!) and used to build an entire GUI that just wraps the CLI commands. This works because the inputs and outputs are strongly typed objects so you can bind UI controls to them trivially. It can also define custom virtual file systems with arbitrary capabilities so you can bind tree navigation controls to your services or whatever. You can “cd” into IIS, Exchange, and SQL and navigate them like they’re a drive. Try that with bash!
评论 #41536351 未加载
评论 #41535992 未加载
评论 #41539469 未加载
评论 #41539913 未加载
评论 #41535954 未加载
评论 #41539124 未加载
评论 #41536115 未加载
评论 #41537697 未加载
rednafi8 个月前
I ask LLMs to modify the shell script to strictly follow Google’s Bash scripting guidelines[^1]. It adds niceties like `set -euo pipefail`, uses `[[…]]` instead of `[…]` in conditionals, and fences all but numeric variables with curly braces. Works great.<p>[^1]: <a href="https:&#x2F;&#x2F;google.github.io&#x2F;styleguide&#x2F;shellguide.html" rel="nofollow">https:&#x2F;&#x2F;google.github.io&#x2F;styleguide&#x2F;shellguide.html</a>
评论 #41539300 未加载
_def8 个月前
&gt; This matches the output format of Bash&#x27;s builtin set -x tracing, but gives the script author more granular control of what is printed.<p>I get and love the idea but I&#x27;d consider this implementation an anti-pattern. If the output mimics set -x but isn&#x27;t doing what that is doing, it can mislead users of the script.
评论 #41539007 未加载
ndsipa_pomu8 个月前
I can highly recommend using bash3boilerplate (<a href="https:&#x2F;&#x2F;github.com&#x2F;kvz&#x2F;bash3boilerplate">https:&#x2F;&#x2F;github.com&#x2F;kvz&#x2F;bash3boilerplate</a>) if you&#x27;re writing BASH scripts and don&#x27;t care about them running on systems that don&#x27;t use BASH.<p>It provides logging facilities with colour usage for the terminal (not for redirecting out to a file) and also decent command line parsing. It uses a great idea to specify the calling parameters in the help&#x2F;usage information, so it&#x27;s quick and easy to use and ensures that you have meaningful information about what parameters the script accepts.<p>Also, please don&#x27;t write shell scripts without running them through ShellCheck. The shell has so many footguns that can be avoided by correctly following its recommendations.
emmelaich8 个月前
Tiny nitpick - usage errors are conventionally &#x27;exit 2&#x27; not &#x27;exit 1&#x27;
bfung8 个月前
Only one that’s shell specific is 4. The rest can be applied any code written. Good work!
评论 #41535834 未加载
denvaar8 个月前
I&#x27;d add that if you&#x27;re going to use color, then you should do the appropriate checks for determining if STDOUT isatty
评论 #41535730 未加载
anthk8 个月前
A tip:<p><pre><code> sh -x $SCRIPT </code></pre> shows a debugging trace on the script in a verbose way, it&#x27;s unvaluable on errors.<p>You can use it as a shebang too:<p><pre><code> #!&#x2F;bin&#x2F;sh -x</code></pre>
评论 #41538659 未加载
jojo_8 个月前
Few months ago, I wrote a bash script for an open-source project.<p>I created a small awk util that I used throughout the script to style the output. I found it very convenient. I wonder if something similar already exists.<p>Some screenshots in the PR: <a href="https:&#x2F;&#x2F;github.com&#x2F;ricomariani&#x2F;CG-SQL-author&#x2F;pull&#x2F;18">https:&#x2F;&#x2F;github.com&#x2F;ricomariani&#x2F;CG-SQL-author&#x2F;pull&#x2F;18</a><p>Let me know guys if you like it. Any comments appreciated.<p><pre><code> function theme() { ! $IS_TTY &amp;&amp; cat || awk &#x27; &#x2F;^([[:space:]]*)SUCCESS:&#x2F; { sub(&quot;SUCCESS:&quot;, &quot; \033[1;32m&amp;&quot;); print; printf &quot;\033[0m&quot;; next } &#x2F;^([[:space:]]*)ERROR:&#x2F; { sub(&quot;ERROR:&quot;, &quot; \033[1;31m&amp;&quot;); print; printf &quot;\033[0m&quot;; next } &#x2F;^ &#x2F; { print; next } &#x2F;^ &#x2F; { print &quot;\033[1m&quot; $0 &quot;\033[0m&quot;; next } &#x2F;^.&#x2F; { print &quot;\033[4m&quot; $0 &quot;\033[0m&quot;; next } { print } END { printf &quot;\033[0;0m&quot; }&#x27; }</code></pre> Go to source: <a href="https:&#x2F;&#x2F;github.com&#x2F;ricomariani&#x2F;CG-SQL-author&#x2F;blob&#x2F;main&#x2F;playground&#x2F;play.sh#L560-L583">https:&#x2F;&#x2F;github.com&#x2F;ricomariani&#x2F;CG-SQL-author&#x2F;blob&#x2F;main&#x2F;playg...</a><p>Example usage:<p><pre><code> exit_with_help_message() { local exit_code=$1 cat &lt;&lt;EOF | theme CQL Playground Sub-commands: help Show this help message hello Onboarding checklist — Get ready to use the playground build-cql-compiler Rebuild the CQL compiler </code></pre> Go to source: <a href="https:&#x2F;&#x2F;github.com&#x2F;ricomariani&#x2F;CG-SQL-author&#x2F;blob&#x2F;main&#x2F;playground&#x2F;play.sh#L26">https:&#x2F;&#x2F;github.com&#x2F;ricomariani&#x2F;CG-SQL-author&#x2F;blob&#x2F;main&#x2F;playg...</a><p><pre><code> cat &lt;&lt;EOF | theme CQL Playground — Onboarding checklist Required Dependencies The CQL compiler $($cql_compiler_ready &amp;&amp; \ echo &quot;SUCCESS: The CQL compiler is ready ($CQL)&quot; || \ echo &quot;ERROR: The CQL compiler was not found. Build it with: $CLI_NAME build-cql-compiler&quot; ) </code></pre> Go to source: <a href="https:&#x2F;&#x2F;github.com&#x2F;ricomariani&#x2F;CG-SQL-author&#x2F;blob&#x2F;main&#x2F;playground&#x2F;play.sh#L114">https:&#x2F;&#x2F;github.com&#x2F;ricomariani&#x2F;CG-SQL-author&#x2F;blob&#x2F;main&#x2F;playg...</a>
fragmede8 个月前
Definitely don&#x27;t check that a variable is non-empty before running<p><pre><code> rm -rf ${VAR}&#x2F;* </code></pre> That&#x27;s typically a great experience for shell scripts!
评论 #41535832 未加载
评论 #41539868 未加载
dvrp8 个月前
These are all about passive experiences (which are great don&#x27;t get me wrong!), but I think you can do better. It&#x27;s the same phenomenon DHH talked about in the Rails doctrine when he said to &quot;Optimize for programmer happiness&quot;.<p>The python excerpt is my favorite example:<p>```<p>$ irb<p>irb(main):001:0&gt; exit<p>$ irb<p>irb(main):001:0&gt; quit<p>$ python<p>&gt;&gt;&gt; exit<p>Use exit() or Ctrl-D (i.e. EOF) to exit<p>```<p>&lt;quote&gt; Ruby accepts both exit and quit to accommodate the programmer’s obvious desire to quit its interactive console. Python, on the other hand, pedantically instructs the programmer how to properly do what’s requested, even though it obviously knows what is meant (since it’s displaying the error message). That’s a pretty clear-cut, albeit small, example of [Principle of Least Surprise]. &lt;&#x2F;quote&gt;
评论 #41537654 未加载
评论 #41537179 未加载
评论 #41537651 未加载
评论 #41537482 未加载
评论 #41537180 未加载
pmarreck8 个月前
Regarding point 1, you should `exit 2` on bad usage, not 1, because it is widely considered that error code 2 is a USAGE error.
latexr8 个月前
&gt; if [ -z &quot;$1&quot; ]<p>I also recommend you catch if the argument is `-h` or `--help`. A careful user won’t just run a script with no arguments in the hopes it does nothing but print the help.¹<p><pre><code> if [[ &quot;${1}&quot; =~ ^(-h|--help)$ ]] </code></pre> Strictly speaking, your first command should indeed `exit 1`, but that request for help should `exit 0`.<p>¹ For that reason, I never make a script which runs without an argument. Except if it <i>only</i> prints information without doing anything destructive or that the user might want to undo. Everything else must be called with an argument, even if a dummy one, to ensure intentionality.
calmbonsai8 个月前
Maybe in the late ‘90s it may have been appropriate to use shell for this (I used Perl for this back then) sort of TUI, but now it’s wrong-headed to use shell for anything aside from bootstrapping into an appropriately dedicated set of TUI libraries such as Python, Ruby, or hell just…anything with proper functions, deps checks, and error-handling.
baby8 个月前
Let&#x27;s normalize using python instead of bash
评论 #41541666 未加载
评论 #41547105 未加载
mkmk8 个月前
I don’t remember where I got it, but I have a simple implementation of a command-line spinner that I use keyboard shortcuts to add to most scripts. Has been a huge quality of life improvement but I wish I could just as seamlessly drop in a progress bar (of course, knowing how far along you are is more complex than knowing you’re still chugging along).
评论 #41535775 未加载
account428 个月前
&gt; Strategic Error Handling with &quot;set -e&quot; and &quot;set +e&quot;<p>I think appending an explicit || true for commands that are ok to fail makes more sense. Having state you need to keep track of just makes things less readable.
watmough8 个月前
Good stuff.<p>One rule I like, is to ensure that, as well as validation, all validated information is dumped in a convenient format prior to running the rest of the script.<p>This is super helpful, assuming that some downstream process will need pathnames, or some other detail of the process just executed.
markus_zhang8 个月前
I was so frustrated by having to enter a lot of information for every new git project (I use a new VM for each project) so I wrote a shell script that automates everything for me.<p>I&#x27;ll probably also combine a few git commands for every commit and push.
评论 #41536063 未加载
teo_zero8 个月前
<p><pre><code> if [ -x &quot;$(command -v gtimeout)&quot; ]; then </code></pre> Interesting way to check if a command is installed. How is it better than the simpler and more common &quot;if command...; then&quot;?
评论 #41538117 未加载
artursapek8 个月前
This post and comment section are a perfect encapsulation of why I&#x27;ll just write a Rust or Go program, not bash, if I want to create a CLI tool that I actually care about.
pmarreck8 个月前
<a href="https:&#x2F;&#x2F;github.com&#x2F;charmbracelet&#x2F;glow">https:&#x2F;&#x2F;github.com&#x2F;charmbracelet&#x2F;glow</a> is pretty nice for stylized TUI output
评论 #41537219 未加载
teo_zero8 个月前
In the 4th section, is there a reason why set +e is inside the loop while set -e is outside, or is it just an error?
评论 #41537907 未加载
Rzor8 个月前
Nicely done. I love everything about this.
Myrmornis8 个月前
In the first example, the error messages should be going to stderr.
worik8 个月前
I liked the commenting style
thangalin8 个月前
The first four parts of my Typesetting Markdown blog describes improving the user-friendliness of bash scripts. In particular, you can use bash to define a reusable script that allows isolating software dependencies, command-line arguments, and parsing.<p><a href="https:&#x2F;&#x2F;dave.autonoma.ca&#x2F;blog&#x2F;2019&#x2F;05&#x2F;22&#x2F;typesetting-markdown-part-1&#x2F;" rel="nofollow">https:&#x2F;&#x2F;dave.autonoma.ca&#x2F;blog&#x2F;2019&#x2F;05&#x2F;22&#x2F;typesetting-markdow...</a><p>In effect, create a list of dependencies and arguments:<p><pre><code> #!&#x2F;usr&#x2F;bin&#x2F;env bash source $HOME&#x2F;bin&#x2F;build-template DEPENDENCIES=( &quot;gradle,https:&#x2F;&#x2F;gradle.org&quot; &quot;warp-packer,https:&#x2F;&#x2F;github.com&#x2F;Reisz&#x2F;warp&#x2F;releases&quot; &quot;linux-x64.warp-packer,https:&#x2F;&#x2F;github.com&#x2F;dgiagio&#x2F;warp&#x2F;releases&quot; &quot;osslsigncode,https:&#x2F;&#x2F;www.winehq.org&quot; ) ARGUMENTS+=( &quot;a,arch,Target operating system architecture (amd64)&quot; &quot;o,os,Target operating system (linux, windows, macos)&quot; &quot;u,update,Java update version number (${ARG_JAVA_UPDATE})&quot; &quot;v,version,Full Java version (${ARG_JAVA_VERSION})&quot; ) </code></pre> The build-template can then be reused to enhance other shell scripts. Note how by defining the command-line arguments as data you can provide a general solution to printing usage information:<p><a href="https:&#x2F;&#x2F;gitlab.com&#x2F;DaveJarvis&#x2F;KeenWrite&#x2F;-&#x2F;blob&#x2F;main&#x2F;scripts&#x2F;build-template?#L186" rel="nofollow">https:&#x2F;&#x2F;gitlab.com&#x2F;DaveJarvis&#x2F;KeenWrite&#x2F;-&#x2F;blob&#x2F;main&#x2F;scripts&#x2F;...</a><p>Further, the same command-line arguments list can be used to parse the options:<p><a href="https:&#x2F;&#x2F;gitlab.com&#x2F;DaveJarvis&#x2F;KeenWrite&#x2F;-&#x2F;blob&#x2F;main&#x2F;scripts&#x2F;build-template?#L186" rel="nofollow">https:&#x2F;&#x2F;gitlab.com&#x2F;DaveJarvis&#x2F;KeenWrite&#x2F;-&#x2F;blob&#x2F;main&#x2F;scripts&#x2F;...</a><p>If you want further generalization, it&#x27;s possible to have the template parse the command-line arguments automatically for any particular script. Tweak the arguments list slightly by prefixing the name of the variable to assign to the option value provided on the CLI:<p><pre><code> ARGUMENTS+=( &quot;ARG_JAVA_ARCH,a,arch,Target operating system architecture (amd64)&quot; &quot;ARG_JAVA_OS,o,os,Target operating system (linux, windows, macos)&quot; &quot;ARG_JAVA_UPDATE,u,update,Java update version number (${ARG_JAVA_UPDATE})&quot; &quot;ARG_JAVA_VERSION,v,version,Full Java version (${ARG_JAVA_VERSION})&quot; ) </code></pre> If the command-line options require running different code, it is possible to accommodate that as well, in a reusable solution.
gjvc8 个月前
literally nothing here of interest