What general tips do you have for golfing in Bash? I'm looking for ideas that can be applied to code golf problems in general that are at least somewhat specific to Bash (e.g. "remove comments" is not an answer). Please post one tip per answer.
43 Answers
Undocumented, but works in every version I've run into for legacy sh
backwards compatibility:
for
loops allow you to use {
}
instead of do
done
. E.g. replace:
for i in {1..10};do echo $i; done
with:
for i in {1..10};{ echo $i;}
-
\$\begingroup\$ what shell is the is
sh
and what shell allows thisfor
syntax? it is expressly allowed inzsh
. \$\endgroup\$– mikeservCommented Dec 23, 2015 at 19:55 -
\$\begingroup\$ @mikeserv Bash. I remember reading somewhere that this syntax was allowed in some old
sh
and that Bash also allows it because of that, though sadly I don't have a citation. \$\endgroup\$ Commented Dec 23, 2015 at 19:57 -
\$\begingroup\$ ahh...
csh
, probably - that's how they worked in that shell. \$\endgroup\$– mikeservCommented Dec 23, 2015 at 20:02 -
\$\begingroup\$ by the way, in
ksh93
the above thing could be :;{1..10}
, and inbash
:printf %s\\n {1..10}
\$\endgroup\$– mikeservCommented Dec 23, 2015 at 20:08 -
4\$\begingroup\$
for((;i++<10)){ echo $i;}
is shorter thanfor i in {1..10};{ echo $i;}
\$\endgroup\$ Commented Jan 19, 2017 at 4:58
For arithmetic expansion use $[…]
instead of $((…))
:
bash-4.1$ echo $((1+2*3))
7
bash-4.1$ echo $[1+2*3]
7
In arithmetic expansions don't use $
:
bash-4.1$ a=1 b=2 c=3
bash-4.1$ echo $[$a+$b*$c]
7
bash-4.1$ echo $[a+b*c]
7
Arithmetic expansion is performed on indexed array subscripts, so don't use $
neither there:
bash-4.1$ a=(1 2 3) b=2 c=3
bash-4.1$ echo ${a[$c-$b]}
2
bash-4.1$ echo ${a[c-b]}
2
In arithmetic expansions don't use ${…}
:
bash-4.1$ a=(1 2 3)
bash-4.1$ echo $[${a[0]}+${a[1]}*${a[2]}]
7
bash-4.1$ echo $[a[0]+a[1]*a[2]]
7
-
\$\begingroup\$ Replacing
while((i--))
, which works, withwhile[i--]
orwhile $[i--]
did not work for me. GNU bash, version 4.3.46(1) \$\endgroup\$ Commented Nov 6, 2016 at 22:54 -
1\$\begingroup\$ Correct, @GlennRanders-Pehrson. That is not supposed to work. \$\endgroup\$ Commented Nov 7, 2016 at 9:08
-
\$\begingroup\$
y=`bc<<<"($x*2.2)%10/1"`
... example of usingbc
for non-integer calculations... note the/1
at the end truncates the resulting decimal to an int. \$\endgroup\$– roblogicCommented Mar 23, 2019 at 12:39 -
\$\begingroup\$
s=$((i%2>0?s+x:s+y))
... example of using ternary operator in bash arithmetic. It's shorter thanif..then..else
or[ ] && ||
\$\endgroup\$– roblogicCommented Mar 23, 2019 at 12:43 -
1\$\begingroup\$ @manatwork Thanks. They must have removed it. I'm on
GNU bash, version 5.0.2(1)-release (x86_64-apple-darwin16.7.0)
and it's not in mine. \$\endgroup\$– JonahCommented Apr 3, 2019 at 16:40
The normal, lengthy and boring way to define a function is
f(){ CODE;}
As this guy found out, you absolutely need the space before CODE
and the semicolon after it.
This is a little trick I've learned from @DigitalTrauma:
f()(CODE)
That is two characters shorter and it works just as well, provided that you don't need to carry over any changes in variables' values after the function returns (the parentheses run the body in a subshell).
As @jimmy23013 points out in the comments, even the parentheses may be unnecessary.
The Bash Reference Manual shows that functions can be defined as follows:
name () compound-command [ redirections ]
or
function name [()] compound-command [ redirections ]
A compound command can be:
- a Looping Construct:
until
,while
orfor
- a Conditional Construct:
if
,case
,((...))
or[[...]]
- Grouped Commands:
(...)
or{...}
That means all of the following are valid:
$ f()if $1;then $2;fi
$ f()($1&&$2)
$ f()(($1)) # This one lets you assign integer values
And I've been using curly brackets like a sucker...
-
2\$\begingroup\$ Note that you can also use
f()while ...
f()if ...
and other compound commands. \$\endgroup\$ Commented Nov 7, 2014 at 6:13 -
\$\begingroup\$ This one surprised me because I thought
f()CODE
was legal. It turns out thatf()echo hi
is legal in pdksh and zsh, but not in bash. \$\endgroup\$– kernighCommented Nov 7, 2014 at 15:50 -
1\$\begingroup\$ its especiallly useful w/
for
since it defaults to positionals:f()for x do : $x; done;set -x *;PS4=$* f "$@"
or something. \$\endgroup\$– mikeservCommented Dec 23, 2015 at 20:21
:
is a command that does nothing, its exit status always succeeds, so it can be used instead of true
.
-
\$\begingroup\$ Using a subshell and piping it would use about the same number of bytes, but piping it would be more practical. \$\endgroup\$ Commented Jan 24, 2017 at 16:05
-
8
-
2\$\begingroup\$ Well at that point
:
isn't the same command anymore, this kind of fork bomb just defines a function called:
and calls it twice.a(){a|a}
is equivalent \$\endgroup\$ Commented Dec 5, 2020 at 7:59
More tips
- Abuse the ternary operator,
((test)) && cmd1 || cmd2
or[ test ] && cmd1 || cmd2
, as much as possible.
Examples (length counts always exclude the top line):
t="$something"
if [ $t == "hi" ];then
cmd1
cmd2
elif [ $t == "bye" ];then
cmd3
cmd4
else
cmd5
if [ $t == "sup" ];then
cmd6
fi
fi
By using ternary operators only, this can easily be shortened to:
t="$something"
[ $t == "hi" ]&&{
cmd1;cmd2
}||[ $t == "bye" ]&&{
cmd3;cmd4
}||{
cmd5
[ $t == "sup" ]&&cmd6
}
As nyuszika7h pointed out in the comments, this specific example could be shortened even further using case
:
t="$something"
case $t in "hi")cmd1;cmd2;;"bye")cmd3;cmd4;;*)cmd5;[ $t == "sup" ]&&cmd6;esac
Also, prefer parentheses to braces as much as possible. Since parentheses are a metacharacter, and not a word, they never require spaces in any context. This also means run as many commands in a subshell as possible, because curly braces (i.e.
{
and}
) are reserved words, not meta-characters, and thus have to have whitespace on both sides to parse correctly, but meta-characters don't. I assume that you know by now that subshells don't affect the parent environment, so assuming that all the example commands can safely be run in a subshell (which isn't typical in any case), you can shorten the above code to this:t=$something [ $t == "hi" ]&&(cmd1;cmd2)||[ $t == "bye" ]&&(cmd3;cmd4)||(cmd5;[ $t == "sup" ]&&cmd6)
Also, if you can't, using parentheses can still minify it some. One thing to keep in mind is that it only works for integers, which renders it useless for the purposes of this example (but it is much better than using -eq
for integers).
One more thing, avoid quotes where possible. Using that above advice, you can further minify it. Example:
t=$something [ $t == hi ]&&(cmd1;cmd2)||[ $t == bye ]&&(cmd3;cmd4)||(cmd5;[ $t == sup ]&&cmd6)
In testing conditions, prefer single brackets to double brackets as much as possible with a few exceptions. It drops two characters for free, but it isn't as robust in some cases (it's a Bash extension - see below for an example). Also, use the single equals argument rather than the double. It is a free character to drop.
[[ $f == b ]]&&: # ... <-- Bad [ $f == b ]&&: # ... <-- Better [ $f = b ]&&: # ... <-- Best. word splits and pathname-expands the contents of $f. Esp. bad if it starts with -
Note this caveat, especially in checking for null output or an undefined variable:
[[ $f ]]&&: # double quotes aren't needed inside [[, which can save chars
[ "$f" = '' ]&&: <-- This is significantly longer
[ -n "$f" ]&&:
In all technicality, this specific example would be best with case
... in
:
t=$something
case $t in hi)cmd1;cmd2;;bye)cmd3;cmd4;;*)cmd5;[ $t == sup ]&&cmd6;esac
So, the moral of this post is this:
- Abuse the boolean operators as much as possible, and always use them instead of
if
/if-else
/etc. constructs. - Use parentheses as much as possible and run as many segments as possible in subshells because parentheses are meta-characters and not reserved words.
- Avoid quotes as much as physically possible.
- Check out
case
...in
, since it may save quite a few bytes, particularly in string matching.
P.S.: Here's a list of meta-characters recognized in Bash regardless of context (and can separate words):
< > ( ) ; & | <space> <tab>
EDIT: As manatwork pointed out, the double parenthesis test only works for integers. Also, indirectly, I found that you need to have whitespace surrounding the ==
operator. Corrected my post above.
I also was too lazy to recalculate the length of each segment, so I simply removed them. It should be easy enough to find a string length calculator online if necessary.
-
1\$\begingroup\$ Sorry to say, but you have some serious errors there.
[ $t=="hi" ]
will always evaluate to 0, as it is parsed as[ -n "STRING" ]
.(($t=="hi"))
will always evaluate to 0 as long as $t has non-numerical value, as strings are forced into integers in arithmetic evaluations. Some test cases: pastebin.com/WefDzWbL \$\endgroup\$ Commented Feb 15, 2014 at 13:30 -
\$\begingroup\$ @manatwork Thanks for the catch. I'll update accordingly. \$\endgroup\$– ClaudiaCommented Feb 17, 2014 at 3:55
-
\$\begingroup\$ Using a
case
would be shorter here. Also, you don't need a space before}
, but you do after{
. \$\endgroup\$– user344Commented Jun 23, 2014 at 15:33 -
2\$\begingroup\$ Why would
=
be less robust than==
?=
is mandated by POSIX,==
isn't. \$\endgroup\$– DennisCommented Sep 5, 2014 at 4:28 -
1\$\begingroup\$ The question does ask for one tip per answer... \$\endgroup\$ Commented Aug 22, 2016 at 11:21
If you need to pass the content of a variable to STDIN of the next process in a pipeline, it is common to echo the variable into a pipeline. But you can achieve the same thing with a <<<
bash here string:
$ s="code golf"
$ echo "$s"|cut -b4-6
e g
$ cut -b4-6<<<"$s"
e g
$
-
4\$\begingroup\$ Since we're golfing,
s=code\ golf
,echo $s|
and<<<$s
(keeping in mind that the latter two work only because there are no repeated spaces, etc.). \$\endgroup\$– DennisCommented Nov 7, 2014 at 3:29
Avoid $( ...command... )
, there is an alternative which saves one char and does the same thing:
` ...command... `
-
12\$\begingroup\$ Sometimes
$( )
is needed if you have nested command substitutions; otherwise you'd have to escape the inner``
\$\endgroup\$ Commented Apr 13, 2014 at 4:18 -
1\$\begingroup\$ These technically do different things, I've had to use the backticks instead of
$()
when I wanted to run the substitution on my machine instead of thescp
target machine, for example. In most cases they're identical. \$\endgroup\$ Commented Apr 21, 2014 at 19:31 -
4\$\begingroup\$ @undergroundmonorail: you never need backticks. Anything they can do,
$()
can do if you quote things properly. (unless you need your command to survive something that munges$
but not backticks). There are some subtle differences in quoting things inside them. mywiki.wooledge.org/BashFAQ/082 explains some differences. Unless you're golfing, never use backticks. \$\endgroup\$ Commented Sep 24, 2015 at 7:09 -
\$\begingroup\$ @PeterCordes I'm sure there was a way but everything I tried at the time didn't work. Even if backticks weren't the best solution, I was glad I knew about them because it was the only solution I had. ¯\_(ツ)_/¯ \$\endgroup\$ Commented Sep 24, 2015 at 7:43
-
\$\begingroup\$ @DigitalTrauma Sample of nesting:
echo `bc <<<"\`date +%s\`-12"`
... (It's hard to post sample containing backtick in comment, there! ;) \$\endgroup\$ Commented Jan 24, 2017 at 17:22
One-line for
loops
An arithmetic expression concatenated with a range expansion will be evaluated for each item in the range. For example the following:
: $[expression]{0..9}
will evaluate expression
10 times.
This is often significantly shorter than the equivalent for
loop:
for((;10>n++;expression with n)){ :;}
: $[expression with ++n]{0..9}
If you don't mind command not found errors, you can remove the inital :
. For iterations larger than 10, you can also use character ranges, for example {A..z}
will iterate 58 times.
As a practical example, the following both produce the first 50 triangular numbers, each on their own line:
for((;50>d++;)){ echo $[n+=d];} # 31 bytes
printf %d\\n $[n+=++d]{A..r} # 28 bytes
-
\$\begingroup\$ you can also iterate backwards:
for((;0<i--;)){ f;}
\$\endgroup\$– roblogicCommented Mar 23, 2019 at 13:21 -
4\$\begingroup\$ Oftentimes you can reuse the range in the loop by producing an
eval
able string. For example, your triangular numbers code could be turned intoeval echo\ $[n+={1..50}]\;
for 26 bytes \$\endgroup\$– Jo King ♦Commented May 20, 2021 at 2:27 -
3\$\begingroup\$ or better yet,
eval $[n+={0..50}]\;echo
for 24 \$\endgroup\$– Jo King ♦Commented Oct 22, 2021 at 12:07 -
\$\begingroup\$ @JoKing: Although the 24 char version also dumps an error trying to
eval
the command0
alone, which the 26 char version avoids. \$\endgroup\$ Commented May 12, 2023 at 19:09
Loop over arguments
As noted in Bash “for” loop without a “in foo bar…” part, the in "$@;"
in for x in "$@;"
is redundant.
From help for
:
for: for NAME [in WORDS ... ] ; do COMMANDS; done
Execute commands for each member in a list.
The `for' loop executes a sequence of commands for each member in a
list of items. If `in WORDS ...;' is not present, then `in "$@"' is
assumed. For each element in WORDS, NAME is set to that element, and
the COMMANDS are executed.
Exit Status:
Returns the status of the last command executed.
For example, if we want to square all numbers given positional arguments to a Bash script or a function, we can do this.
for n;{ echo $[n*n];}
Instead of grep -E
, grep -F
, grep -r
, use egrep
, fgrep
, rgrep
, saving two chars. The shorter ones are deprecated but work fine.
(You did ask for one tip per answer!)
-
1\$\begingroup\$ Too bad there's no
Pgrep
forgrep -P
. Although I see how it could be easily confused withpgrep
, which is used to look up processes. \$\endgroup\$– user344Commented Apr 21, 2014 at 19:21 -
1\$\begingroup\$ @nyuszika7h I personally use
grep -o
a lot \$\endgroup\$– user16402Commented Apr 22, 2014 at 6:55 -
\$\begingroup\$ Wouldn't it save 3 including the space? \$\endgroup\$ Commented Jan 24, 2017 at 16:04
Element 0 of an array may be accessed with the variable name only, a five byte saving over explicitly specifying an index of 0:
$ a=(code golf)
$ echo ${a[0]}
code
$ echo $a
code
$
Use arithmetic (( ... ))
for conditions
You could replace:
if [ $i -gt 5 ] ; then
echo Do something with i greater than 5
fi
by
if((i>5));then
echo Do something with i greater than 5
fi
(Note: There is no space after if
)
or even
((i>5))&&{
echo Do something with i greater than 5
}
... or if only one command
((i>5))&&echo Echo or do something with i greater than 5
Further: Hide variable setting in arithmetic construct:
((i>5?c=1:0))&&echo Nothing relevant there...
# ...
((c))&&echo Doing something else if i was greater than 5
or same
((c=i>5?c=0,1:0))&&echo Nothing relevant there...
# ...
((c))&&echo Doing something else if i was greater than 5
... where if i > 5, then c = 1 (not 0;)
-
\$\begingroup\$ You could save 2 bytes by using
[ ]
instead of(())
. \$\endgroup\$ Commented Jan 24, 2017 at 15:06 -
\$\begingroup\$ @ckjbgames Are you sure of that!? Wich bash version are you using? \$\endgroup\$ Commented Jan 24, 2017 at 15:33
-
\$\begingroup\$ @FHauri I guess it would be about the same in terms of bytes. \$\endgroup\$ Commented Jan 24, 2017 at 15:56
-
\$\begingroup\$ @ckjbgames With
[ ]
you need the dollar sign for variable. I don't see how you could do same with same or smaller length by using[ ]
. \$\endgroup\$ Commented Jan 24, 2017 at 16:55 -
\$\begingroup\$ Bonus Tip: if the first line of a for loop begins with
((...))
, no newline or space is necessary. E.g.for((;10>n++;)){((n%3))&&echo $n;}
Try it online! \$\endgroup\$– primoCommented Dec 8, 2018 at 9:41
A shorter syntax for infinite loops (which can be escaped with break
or exit
statements) is
for((;;)){ code;}
This is shorter than while true;
and while :;
.
If you don't need break
(with exit
as the only way to escape), you can use a recursive function instead.
f(){ code;f;};f
If you do need break, but you don't need exit and you don't need to carry over any variable modification outside the loop, you can use a recursive function with parentheses around the body, which run the function body in a subshell.
f()(code;f);f
Use if
to group commands
Compared to this tip which removes the if
at all, this should only work better in some very rare cases, such as when you need the return values from the if
.
If you have a command group which ends with a if
, like these:
a&&{ b;if c;then d;else e;fi;}
a&&(b;if c;then d;else e;fi)
You can wrap the commands before if
in the condition instead:
a&&if b;c;then d;else e;fi
Or if your function ends with a if
:
f(){ a;if b;then c;else d;fi;}
You can remove the braces:
f()if a;b;then c;else d;fi
-
1\$\begingroup\$ You could use the ternary operator
[test] && $if_true || $else
in these functions and save some bytes. \$\endgroup\$ Commented Jan 24, 2017 at 15:09 -
\$\begingroup\$ Also, you don't need spaces around
&&
and||
\$\endgroup\$– roblogicCommented Mar 23, 2019 at 8:18
Sometimes it is shorter to use the expr
builtin for displaying the result of a simple arithmetic expression instead of the usual echo $[ ]
. For example:
expr $1 % 2
is one byte shorter than:
echo $[$1%2]
Use [
instead of [[
and test
when possible
Example:
[ -n $x ]
Use =
instead of ==
for comparison
Example:
[ $x = y ]
Note that you must have spaces around the equals sign or else it won't work. Same applies to ==
based on my tests.
-
3\$\begingroup\$ The
[
vs.[[
may depend on the amount of required quotes: pastebin.com/UPAGWbDQ \$\endgroup\$ Commented Apr 22, 2014 at 5:56 -
\$\begingroup\$ @manatwork That's a good point. \$\endgroup\$– user344Commented Apr 22, 2014 at 19:02
-
2\$\begingroup\$ General rule:
[
...]
==/bin/test
, but[[
...]]
!=/bin/test
and one should never prefer[
...]
over[[
...]]
outside of codegolf \$\endgroup\$– catCommented Dec 26, 2015 at 22:23
Alternative to cat
Say you are trying to read a file and use it in something else. What you might do is:
echo foo `cat bar`
If the contents of bar
was foobar
, this would print foo foobar
.
However, there is an alternative if you are using this method, which saves 3 bytes:
echo foo `<bar`
-
1\$\begingroup\$ Is there a reason why
<bar
by itself does not work but placing it in backticks does? \$\endgroup\$ Commented Oct 19, 2018 at 18:05 -
\$\begingroup\$ @Cowsquack Yes. The
<
puts a file to a command, but in this case it puts it into standard output due to a quirk. The backticks evaluate this together. \$\endgroup\$– OkxCommented Oct 19, 2018 at 19:44 -
\$\begingroup\$ Is there a shorter way to read from standard input other than
`cat`
? \$\endgroup\$– JoelCommented Oct 3, 2019 at 21:21 -
Shorten file names
In a recent challenge I was trying to read the file /sys/class/power_supply/BAT1/capacity
, however this can be shortened to /*/*/*/*/capac*y
as no other file exists with that format.
For example, if you had a directory foo/
containing the files foo, bar, foobar, barfoo
and you wanted to reference the file foo/barfoo
, you can use foo/barf*
to save a byte.
The *
represents "anything", and is equivalent to the regex .*
.
tr -cd
is shorter than grep -o
For example, if you need to count spaces, grep -o <char>
(print only the matched) gives 10 bytes while tr -cd <char>
(delete complement of <char>
) gives 9.
# 16 bytes
grep -o \ |wc -l
# 15 bytes
tr -cd \ |wc -c
(source)
Note that they both give slightly different outputs. grep -o
returns line separated results while tr -cd
gives them all on the same line, so tr
might not always be favourable.
-
1\$\begingroup\$ To concatenate lines, instead of
tr -cd \\n
you can dors -g0
\$\endgroup\$– roblogicCommented Dec 27, 2022 at 6:09
Expand away the tests
Essentially, the shell is a kind of macro language, or at least a hybrid or some kind. Every command-line can be basically broken into two parts: the parsing/input part and the expansion/output part.
The first part is what most people focus on because it's the most simple: you see what you get. The second part is what many avoid ever even trying to understand very well and is why people say things like eval
is evil and always quote your expansions - people want the result of the first part to equal the first. That's ok - but it leads to unnecessarily long code branches and tons of extraneous testing.
Expansions are self-testing. The ${param[[:]#%+-=?]word}
forms are more than enough to validate the contents of a parameter, are nestable, and are all based around evaluating for NUL - which is what most people expect of tests anyway. +
can be especially handy in loops:
r()while IFS= read -r r&&"${r:+set}" -- "$@" "${r:=$*}";do :;done 2>&-
IFS=x
printf %s\\n some lines\ of input here '' some more|{ r;echo "$r"; }
somexlines ofxinputxhere
...while read
pulls in not blank lines "${r:+set}"
expands to "set"
and the positionals get $r
appended. But when a blank line is read
, $r
is empty and "${r:+set}"
expands to ""
- which is an invalid command. But because the command-line is expanded before the ""
null command is searched, "${r:=$*}"
takes the values of all of the positionals concatenated on the first byte in $IFS
as well. r()
could be called again in |{
compound command ;}
w/ a different value for $IFS
to get the next input paragraph as well, since it is illegal for a shell's read
to buffer beyond the next \n
ewline in input.
Use tail recursion to make loops shorter:
These are equivalent in behavior (though probably not in memory/PID usage):
while :;do body; done
f()(body;f);f
body;exec $0
body;$0
And these are roughly equivalent:
while condition; do body; done
f()(body;condition&&f);f
body;condition&&exec $0
body;condition&&$0
(technically the last three will always execute the body at least once)
Using $0
requires your script to be in a file, not pasted into the bash prompt.
Eventually your stack might overflow, but you save some bytes.
Alternatives to head
line
is three bytes shorter than head -1
, but is being deprecated.
sed q
is two bytes shorter than head -1
.
sed 9q
is one byte shorter than head -9
.
-
1
split
has another (deprecated, but nobody cares) syntax for splitting input into sections of N
lines each: instead of split -lN
you can use split -N
e.g. split -9
.
Use pwd
instead of echo
to generate a line of output
Need to put a line on stdout but don't care about the contents, and want to restrict your answer to shell builtins? pwd
is a byte shorter than echo
.
Quotes can be omitted when printing strings.
echo "example"
echo example
Output in SM-T335 LTE, Android 5.1.1:
u0_a177@milletlte:/ $ echo "example"
example
u0_a177@milletlte:/ $ echo example
example
Abuse function definitions to check for equality
If you want to check if two strings are equal to each other, the obvious way is to use the [[
/test
/[
builtins:
[[ $a == b ]]
[[ $a = b ]]
[ $a = b ]
But in fact often a shorter way is to define a do-nothing function under the name of b
and then try to call it with $a
.
b()(:);$a
# zsh only:
b():;$a
Depending on the possible values $a
can take, you might need to add some characters to ensure that it never coincides with the name of a real command:
,b()(:);,$a
# zsh only:
,b();,$a
If you're testing and immediately using &&
, then you can put generally put the command after the &&
as the body of the function:
[ $a = b ]&&foo
b()(foo);$a
# zsh only:
b()foo;$a
Here are a few answers I've abused this in (Zsh not Bash, but the same idea applies):
-
\$\begingroup\$ you can also do
${a/$b}
and check if the result is an empty string \$\endgroup\$– roblogicCommented Sep 1, 2023 at 16:38
Use $_
(the underscore variable)
The special shell variable $_
always contains the last argument to the most recently executed command. If you have any sort of immediate repetition in your code, it can probably be reduced with this.
touch hello;cat hello
touch hello;cat $_
More details can be found in this Unix & Linux question.
(I can't believe this hasn't been posted before, and, as far as I can tell, barely ever even been used in code golf before!)
-
\$\begingroup\$
$_
isn't reliably populated, for example incase $_
it's empty \$\endgroup\$– roblogicCommented Dec 27, 2022 at 16:22
Output a string based on a test string or numeric test with expr
From my question:
For any form of test expr
supports (numerical or string equality, inequality, less than, greater than, regex pattern match, with support for math), there are often ways to use it to improve on the naive approach to echoing a particular string based on a given test. For example, the most straightforward approach to performing an "is a program argument a substring of a fixed string?" test, echoing the string true
or false
based on the result, assuming the substring contains no regex special characters, is:
[[ FIXEDSTRING =~ $1 ]]&&echo true||echo false
But with expr
, we can shave off six characters by careful use of &
and |
to select the string that results from the overall test:
expr true \& FIXEDSTRING : .*$1 \| false
Changing the *
to \*
(and costing a character) if there is a possibility that the command will be run in a directory where .*$1
would match existing files.
The ordering is important here; \&
evaluates to the first argument (not the second like in Python and similar language) when the overall "and" expression is true, so true \&
must come before the pattern match test FIXEDSTRING : .*$1
. The .*
is needed because pattern matching is implicitly anchored at the beginning, as with ^
, so the .*
lets it match anywhere. And no, sadly, you can't remove any of these spaces; expr
expects each operand and operator to be a completely separate argument, so it doesn't need to do any tokenizing of its own, which means the spaces must be there so the shell can tokenize it.
For the specific case of a regex match, you can shorten it four more characters with sed
, :
sed "/$1/ctrue
cfalse"<<<FIXEDSTRING
Similar uses allow replacing the various forms of numerical test and echo:
[ $1 -gt 0 ]&&echo yes||echo no
[[ $1>0 ]]&&echo yes||echo no
(($1>0))&&echo yes||echo no
with:
expr yes \& $1 \> 0 \| no
which, even with the need to escape the >
, and despite numerical tests with (())
allowing you to drop all whitespace, is still two characters shorter than the shortest of the naive approaches. If the comparison is more complex, you can mix-and-match:
expr yes \& $[$1>0] \| no
is the same length as the purely expr
-based approach, but grows more slowly in more complex cases (as fewer things need escaping, and spaces aren't needed between terms).
Use a pipe to the :
command instead of /dev/null
. The :
built-in will eat all its input.
-
4\$\begingroup\$ No, it will crash the program with SIGPIPE in most cases.
echo a|tee /dev/stderr|:
will not print anything. \$\endgroup\$ Commented Nov 7, 2014 at 6:27 -
1\$\begingroup\$ There is a race:
echo a|tee /dev/stderr|:
did print a on my computer, but elsewhere SIGPIPE might kill tee first. It might depend on version oftee
. \$\endgroup\$– kernighCommented Nov 7, 2014 at 15:56 -
\$\begingroup\$ Yes, it's a SIGPIPE problem:
tee >(:) < <(seq 1 10)
will work, buttee /dev/stderr | :
won't. Evena() { :;};tee /dev/stderr < <(seq 1 10)| a
don't print anything. \$\endgroup\$ Commented Jan 24, 2017 at 17:20 -
\$\begingroup\$ @user16402 - you should have a fccing name to my purview... anyway, the
:
intrinsic eats not at all... if you supposit input to colon you might flood a pipe to in out error... but you can float a redirect by a colon, or drop a process with it...:| while i>&$(($??!$?:${#?})) command shit; do [ -s testitsoutput ]; done
or however that pseudo suggestion applies... also, are you aware youre nearly so ghosty as me? ... avoid at all costs the< <(psycho shit i can alias to crazy math eat your world; okay? anyway, ksh93 has a separate but equal composite char placement)
\$\endgroup\$– mikeservCommented Sep 10, 2019 at 2:04
When assigning noncontinuous array items, you can still skip the successive indices of continuous chunks:
bash-4.4$ a=([1]=1 [2]=2 [3]=3 [21]=1 [22]=2 [23]=3 [31]=1)
bash-4.4$ b=([1]=1 2 3 [21]=1 2 3 [31]=1)
The result is the same:
bash-4.4$ declare -p a b
declare -a a=([1]="1" [2]="2" [3]="3" [21]="1" [22]="2" [23]="3" [31]="1")
declare -a b=([1]="1" [2]="2" [3]="3" [21]="1" [22]="2" [23]="3" [31]="1")
According to man bash
:
Arrays are assigned to using compound assignments of the form name=(value1 ... valuen), where each value is of the form [subscript]=string. Indexed array assignments do not require anything but string. When assigning to indexed arrays, if the optional brackets and subscript are supplied, that index is assigned to; otherwise the index of the element assigned is the last index assigned to by the statement plus one.
-
\$\begingroup\$ Helpful to add: uninitialised elements will expand to 0 in arithmetic expansions and "" in other expansions. \$\endgroup\$ Commented Jun 29, 2017 at 15:47