bash-hackers-wiki/commands/builtin/unset/index.html

101 lines
51 KiB
HTML
Raw Normal View History

<!doctype html><html lang=en class=no-js> <head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link href=https://flokoe.github.io/bash-hackers-wiki/commands/builtin/unset/ rel=canonical><link href=../trap/ rel=prev><link href=../wait/ rel=next><link rel=icon href=../../../assets/images/favicon.png><meta name=generator content="mkdocs-1.6.1, mkdocs-material-9.5.44"><title>The unset builtin command - The Bash Hackers Wiki</title><link rel=stylesheet href=../../../assets/stylesheets/main.0253249f.min.css><link rel=stylesheet href=../../../assets/stylesheets/palette.06af60db.min.css><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link rel=stylesheet href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback"><style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style><script>__md_scope=new URL("../../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script></head> <body dir=ltr data-md-color-scheme=default data-md-color-primary=indigo data-md-color-accent=indigo> <input class=md-toggle data-md-toggle=drawer type=checkbox id=__drawer autocomplete=off> <input class=md-toggle data-md-toggle=search type=checkbox id=__search autocomplete=off> <label class=md-overlay for=__drawer></label> <div data-md-component=skip> <a href=#the-unset-builtin-command class=md-skip> Skip to content </a> </div> <div data-md-component=announce> </div> <header class=md-header data-md-component=header> <nav class="md-header__inner md-grid" aria-label=Header> <a href=../../.. title="The Bash Hackers Wiki" class="md-header__button md-logo" aria-label="The Bash Hackers Wiki" data-md-component=logo> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg> </a> <label class="md-header__button md-icon" for=__drawer> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg> </label> <div class=md-header__title data-md-component=header-title> <div class=md-header__ellipsis> <div class=md-header__topic> <span class=md-ellipsis> The Bash Hackers Wiki </span> </div> <div class=md-header__topic data-md-component=header-topic> <span class=md-ellipsis> The unset builtin command </span> </div> </div> </div> <form class=md-header__option data-md-component=palette> <input class=md-option data-md-color-media data-md-color-scheme=default data-md-color-primary=indigo data-md-color-accent=indigo aria-label="Switch to dark mode" type=radio name=__palette id=__palette_0> <label class="md-header__button md-icon" title="Switch to dark mode" for=__palette_1 hidden> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg> </label> <input class=md-option data-md-color-media data-md-color-scheme=slate data-md-color-primary=indigo data-md-color-accent=indigo aria-label="Switch to light mode" type=radio name=__palette id=__palette_1> <label class="md-header__button md-icon" title="Switch to light mode" for=__palette_0 hidden> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12s-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg> </label> </form> <script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var med
</code></pre></div> <h2 id=description>Description<a class=headerlink href=#description title="Permanent link">&para;</a></h2> <p>The <code>unset</code> builtin command is used to unset values and attributes of shell variables and functions. Without any option, <code>unset</code> tries to unset a variable first, then a function.</p> <h3 id=options>Options<a class=headerlink href=#options title="Permanent link">&para;</a></h3> <table> <thead> <tr> <th>Option</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>-f</code></td> <td>treats each <code>NAME</code> as a function name</td> </tr> <tr> <td><code>-v</code></td> <td>treats each <code>NAME</code> as a variable name</td> </tr> <tr> <td><code>-n</code></td> <td>treats each <code>NAME</code> as a name reference and unsets the variable itself rather than the variable it references</td> </tr> </tbody> </table> <h3 id=exit-status>Exit status<a class=headerlink href=#exit-status title="Permanent link">&para;</a></h3> <table> <thead> <tr> <th>Status</th> <th>Reason</th> </tr> </thead> <tbody> <tr> <td>0</td> <td>no error</td> </tr> <tr> <td>!=0</td> <td>invalid option</td> </tr> <tr> <td>!=0</td> <td>invalid combination of options (<code>-v</code> <strong>and</strong> <code>-f</code>)</td> </tr> <tr> <td>!=0</td> <td>a given <code>NAME</code> is read-only</td> </tr> </tbody> </table> <h2 id=examples>Examples<a class=headerlink href=#examples title="Permanent link">&para;</a></h2> <div class=highlight><pre><span></span><code>unset -v EDITOR
unset -f myfunc1 myfunc2
</code></pre></div> <h3 id=scope>Scope<a class=headerlink href=#scope title="Permanent link">&para;</a></h3> <p>In bash, unset has some interesting properties due to its unique dynamic scope. If a local variable is both declared and unset (by calling unset on the local) from within the same function scope, then the variable appears unset to that scope and all child scopes until either returning from the function, or another local variable of the same name is declared underneath where the original variable was unset. In other words, the variable looks unset to everything until returning from the function in which the variable was set (and unset), at which point variables of the same name from higher scopes are uncovered and accessible once again.</p> <p>If however unset is called from a child scope relative to where a local variable has been set, then the variable of the same name in the next-outermost scope becomes visible to its scope and all children - as if the variable that was unset was never set to begin with. This property allows looking upwards through the stack as variable names are unset, so long as unset and the local it unsets aren't together in the same scope level.</p> <p>Here's a demonstration of this behavior.</p> <div class=highlight><pre><span></span><code>#!/usr/bin/env bash
FUNCNEST=10
# Direct recursion depth.
# Search up the stack for the first non-FUNCNAME[1] and count how deep we are.
callDepth() {
# Strip &quot;main&quot; off the end of FUNCNAME[@] if current function is named &quot;main&quot; and
# Bash added an extra &quot;main&quot; for non-interactive scripts.
if [[ main == !(!(&quot;${FUNCNAME[1]}&quot;)|!(&quot;${FUNCNAME[-1]}&quot;)) &amp;&amp; $- != *i* ]]; then
local -a &#39;fnames=(&quot;${FUNCNAME[@]:1:${#FUNCNAME[@]}-2}&quot;)&#39;
else
local -a &#39;fnames=(&quot;${FUNCNAME[@]:1}&quot;)&#39;
fi
if (( ! ${#fnames[@]} )); then
printf 0
return
fi
local n
while [[ $fnames == ${fnames[++n]} ]]; do
:
done
printf -- $n
}
# This function is the magic stack walker.
unset2() {
unset -v -- &quot;$@&quot;
}
f() {
local a
if (( (a=$(callDepth)) &lt;= 4 )); then
(( a == 1 )) &amp;&amp; unset a
(( a == 2 )) &amp;&amp; declare -g a=&#39;global scope yo&#39;
f
else
trap &#39;declare -p a&#39; DEBUG
unset2 a # declare -- a=&quot;5&quot;
unset a a # declare -- a=&quot;4&quot;
unset a # declare -- a=&quot;2&quot;
unset a # ./unset-tests: line 44: declare: a: not found
: # declare -- a=&quot;global scope yo&quot;
fi
}
a=&#39;global scope&#39;
f
# vim: set fenc=utf-8 ff=unix ts=4 sts=4 sw=4 ft=sh nowrap et:
</code></pre></div> <p>output:</p> <div class=highlight><pre><span></span><code>declare -- a=&quot;5&quot;
declare -- a=&quot;4&quot;
declare -- a=&quot;2&quot;
./unset-tests: line 44: declare: a: not found
declare -- a=&quot;global scope yo&quot;
</code></pre></div> <p>Some things to observe:</p> <ul> <li><code>unset2</code> is only really needed once. We remain 5 levels deep in <code>f</code>'s for the remaining <code>unset</code> calls, which peel away the outer layers of <code>a</code>'s.</li> <li>Notice that the "a" is unset using an ordinary unset command at recursion depth 1, and subsequently calling unset reveals a again in the global scope, which has since been modified in a lower scope using declare -g.</li> <li>Declaring a global with declare -g bypasses all locals and sets or modifies the variable of the global scope (outside of all functions). It has no affect on the visibility of the global.</li> <li>This doesn't apply to individual array elements. If two local arrays of the same name appear in different scopes, the entire array of the inner scope needs to be unset before any elements of the outer array become visible. This makes "unset" and "unset2" identical for individual array elements, and for arrays as a whole, unset and unset2 behave as they do for scalar variables.</li> </ul> <h3 id=args>Args<a class=headerlink href=#args title="Permanent link">&para;</a></h3> <p>Like several other Bash builtins that take parameter names, unset expands its arguments.</p> <div class=highlight><pre><span></span><code> ~ $ ( a=({a..d}); unset &#39;a[2]&#39;; declare -p a )
declare -a a=&#39;([0]=&quot;a&quot; [1]=&quot;b&quot; [3]=&quot;d&quot;)&#39;
</code></pre></div> <p>As usual in such cases, it's important to quote the args to avoid accidental results such as globbing.</p> <div class=highlight><pre><span></span><code> ~ $ ( a=({a..d}) b=a c=d d=1; set -x; unset &quot;${b}[&quot;{2..3}-c\]; declare -p a )
+ unset &#39;a[2-1]&#39; &#39;a[3-1]&#39;
+ declare -p a
declare -a a=&#39;([0]=&quot;a&quot; [3]=&quot;d&quot;)&#39;
</code></pre></div> <p>Of course hard to follow indirection is still possible whenever arithmetic is involved, also as shown above, even without extra expansions.</p> <p>In Bash, the <code>unset</code> builtin only evaluates array subscripts if the array itself is set.</p> <div class=highlight><pre><span></span><code> ~ $ ( unset -v &#39;a[$(echo a was set &gt;&amp;2)0]&#39; )
~ $ ( a=(); unset -v &#39;a[$(echo a was set &gt;&amp;2)0]&#39; )
a was set
</code></pre></div> <h2 id=portability-considerations>Portability considerations<a class=headerlink href=#portability-considerations title="Permanent link">&para;</a></h2> <p>Quoting POSIX:</p> <div class=highlight><pre><span></span><code>If neither -f nor -v is specified, name refers to a variable; if a variable by that name does not exist, it is unspecified whether a function by that name, if any, shall be unset.
</code></pre></div> <p>Therefore, it is recommended to explicitly specify <code>-f</code> or <code>-v</code> when using <code>unset</code>. Also, I prefer it as a matter of style.</p> <h2 id=see-also>See also<a class=headerlink href=#see-also title="Permanent link">&para;</a></h2> <ul> <li><a href=../declare/ >declare</a></li> <li><a href=./ >unset</a></li> <li><a href=http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_29>POSIX <code>unset</code> utility</a></li> </ul> <aside class=md-source-file> <span class=md-source-file__fact> <span class=md-icon title="Last update"> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M21 13.1c-.1 0-.3.1-.4.2l-1 1 2.1 2.1 1-1c.2-.2.2-.6 0-.8l-1.3-1.3c-.1-.1-.2-.2-.4-.2m-1.9 1.8-6.1 6V23h2.1l6.1-6.1zM12.5 7v5.2l4 2.4-1 1L11 13V7zM11 21.9c-5.1-.5-9-4.8-9-9.9C2 6.5 6.5 2 12 2c5.3 0 9.6 4.1 10 9.3-.3-.1-.6-.2-1-.2s-.7.1-1 .2C19.6 7.2 16.2 4 12 4c-4.4 0-8 3.6-8 8 0 4.1 3.1 7.5 7.1 7.9l-.1.2z"/></svg> </span> <span class="git-revision-date-localized-plugin git-revision-date-localized-plugin-date">November 7, 2024</span> </span> <span class=md-source-file__fact> <span class=md-icon title=Created> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M14.47 15.08 11 13V7h1.5v5.25l3.08 1.83c-.41.28-.79.62-1.11 1m-1.39 4.84c-.36.05-.71.08-1.08.08-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8c0 .37-.03.72-.08 1.08.69.1 1.33.32 1.92.64.1-.56.16-1.13.16-1.72 0-5.5-4.5-10-10-10S2 6.5 2 12s4.47 10 10 10c.59 0 1.16-.06 1.72-.16-.32-.59-.54-1.23-.64-1.92M18 15v3h-3v2h3v3h2v-3h3v-2h-3v-3z"/></svg> </span> <span class="git-revision-date-localized-plugin git-revision-date-localized-plugin-date">November 7, 2024</span> </span> </aside> <h2 id=__comments>Comments</h2> <script src=https://giscus.app/client.js data-repo=flokoe/bash-hackers-wiki data-repo-id=R_kgDOJ3Nr6Q data-category="Giscus Page Comments" data-category-id=DIC_kwDOJ3Nr6c4CXq9t data-mapping=pathname data-strict=1 data-reactions-enabled=1 data-emit-metadata=0 data-input-position=top data-theme=preferred_color_scheme data-lang=en data-loading=lazy crossorigin=anonymous async>
</script> <script>
var giscus = document.querySelector("script[src*=giscus]")
/* Set palette on initial load */
var palette = __md_get("__palette")
if (palette && typeof palette.color === "object") {
var theme = palette.color.scheme === "slate" ? "dark" : "light"
giscus.setAttribute("data-theme", theme)
}
/* Register event handlers after documented loaded */
document.addEventListener("DOMContentLoaded", function() {
var ref = document.querySelector("[data-md-component=palette]")
ref.addEventListener("change", function() {
var palette = __md_get("__palette")
if (palette && typeof palette.color === "object") {
var theme = palette.color.scheme === "slate" ? "dark" : "light"
/* Instruct Giscus to change theme */
var frame = document.querySelector(".giscus-frame")
frame.contentWindow.postMessage(
{ giscus: { setConfig: { theme } } },
"https://giscus.app"
)
}
})
})
</script> </article> </div> <script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script> </div> <button type=button class="md-top md-icon" data-md-component=top hidden> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg> Back to top </button> </main> <footer class=md-footer> <div class="md-footer-meta md-typeset"> <div class="md-footer-meta__inner md-grid"> <div class=md-copyright> Made with <a href=https://squidfunk.github.io/mkdocs-material/ target=_blank rel=noopener> Material for MkDocs </a> </div> </div> </div> </footer> </div> <div class=md-dialog data-md-component=dialog> <div class="md-dialog__inner md-typeset"></div> </div> <script id=__config type=application/json>{"base": "../../..", "features": ["navigation.instant", "navigation.tracking", "navigation.tabs", "navigation.sections", "navigation.top", "content.action.view", "content.action.edit", "search.suggest", "search.highlight", "content.code.copy"], "search": "../../../assets/javascripts/workers/search.6ce7567c.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script> <script src=../../../assets/javascripts/bundle.83f73b43.min.js></script> </body> </html>