61
\$\begingroup\$

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.

\$\endgroup\$

41 Answers 41

1
2
2
\$\begingroup\$

In Pure Bash (or any other pure shellscript), use . for looping.

For example, if you want to display each value of 1^2, 2^2, 3^2, ..., 999^2, 1000^2,

echo $[++x*x]
((x>999))||. x

(where filename is x) is shorter than

for((;x++<1000;));{
echo $[x*x]
}

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ I noticed this can be applied to non-pure shell script too. \$\endgroup\$
    – user100411
    Nov 13 '21 at 7:04
1
\$\begingroup\$

Doing 2 embed loop with 1 for instruction:

for ((l=i=0;l<=99;i=i>98?l++,0:++i)) ;do
    printf "I: %2d, L: %2d\n" $i $l
done |
    tee >(wc) | (head -n4;echo ...;tail -n 5)
I:  0, L:  0
I:  1, L:  0
I:  2, L:  0
I:  3, L:  0
...
I: 96, L: 99
I: 97, L: 99
I: 98, L: 99
I: 99, L: 99
  10000   40000  130000
\$\endgroup\$
1
\$\begingroup\$

Assign and Print quoted strings

If you want to assign a quoted string to a variable, and then print the value of that variable, then the usual way to do that would be:

a="Programming Puzzles & Code Golf";echo $a

If a was previously unset, this may be shortened to:

echo ${a=Programming Puzzles & Code Golf}

If a was previously set, then this should be used instead:

echo ${a+Programming Puzzles & Code Golf}

Note this is only useful if the string requires quotes (e.g. contains whitespace). Without quotes, a=123;echo $a is just as short.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ ${a+foo} doesn't set a. \$\endgroup\$ Oct 20 '19 at 6:28
1
\$\begingroup\$

In a case statement, it is valid to omit the last ';;' - which saves 2 bytes:

#!/bin/sh

case $I in
0)cmdA;;
1)cmdB;;
*)cmdC
esac
\$\endgroup\$
1
\$\begingroup\$

Use {..} expansions with eval

Occasionally loops can be shortened by using eval with a brace expansion. For example, to run a command foo with all letters a-z sequentially; instead of:

for x in {a..z};{ foo $x;}
# or, zsh only:
for x ({a..z})foo $x
eval foo\ {a..z}\;
# or, zsh only:
eval ';foo '{a..z}

If you need to reuse the loop variable, it tends to be shorter to use a normal for-loop though.

I can't remember exactly the first answer I saw to do this, but I now use it a lot (those examples are all Zsh because it's obviously superior to Bash, but it's a similar general idea).

\$\endgroup\$
1
\$\begingroup\$

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!)

\$\endgroup\$
0
\$\begingroup\$

Changing behaviour of += by setting integer flag

Try this many times:

unset i
i=1
for a in {1..10} ;do
    i+=1
    ((RANDOM%9)) || declare -i i
  done
echo $i
11117

or in one line

unset i;i=1;for a in {1..10};do i+=1;((RANDOM%9))||declare -i i;done;echo $i

This could answer something between 20 to 11111111111...

\$\endgroup\$
0
\$\begingroup\$

This work under simple shell

cd `mktemp -d` &&>'c'a't'
ln -s /proc/loadavg /proc/uptime .

Then now

procs=$(*)
echo $procs
0.30 0.08 0.03 1/612 31671 322787.60 1259967.99

Nota: Of course, >cat could be written >$(echo -e \\0143\\0141t)

Care, from there, you could encouter some issues due to Locales!

Goto last demo using LC_ALL=C

Same way:

mv cat grep
mv loadavg Mem
ln -s /proc/meminfo Zdatas
rm uptime

mems=$(*)
echo $mems 
MemTotal: 16386788 kB MemFree: 9816320 kB MemAvailable: 12144892 kB

or worst...

mv grep sed
mv Mem s+\\\(Mem\\\|Swap\\\).\*\:++p\;d

then

memsw=$(*)
echo $memsw 
16386788 kB 9834080 kB 12163596 kB 0 kB 20971516 kB 20971516 kB

So simple is this!

declare -p procs mems memsw
declare -- procs="0.14 0.12 0.05 1/612 32078
324221.56 1265616.26"
declare -- mems="MemTotal:       16386788 kB
MemFree:         9832212 kB
MemAvailable:   12161688 kB"
declare -- memsw="       16386788 kB
         9834080 kB
   12163596 kB
            0 kB
      20971516 kB
       20971516 kB"

Last test using LANG=C

(or not)

cd `mktemp -d` &&>'c'a't'
ln -s /proc/meminfo zdatas
cp c* y+-+-+\;s+\\\(Mem\\\|Swap\\\).\*\:\ \*++p\;d
rename 'y/act/esd/' ???
*

may render something like

378908 kB
31940 kB
217924 kB
0 kB
102396 kB
102396 kB

Or

cd `mktemp -d`
ln -s +++\;s+\\\(Mem\\\|Swap\\\).\*\:\ \*++p\;d /proc/me*o ed .
rename 's/^/chr(113+(20>length?1.3*length:8))/e' *
*
\$\endgroup\$
1
  • \$\begingroup\$ or printf -vv %b \\014{3,1} t&&>$v under bash \$\endgroup\$
    – F. Hauri
    Feb 2 '19 at 14:09
0
\$\begingroup\$

To print a range of characters (shorter than echo {a..z}|tr -d ' '):

printf "%s" {a..z}

Print range in reverse order (shorter than printf "%s" {a..z}|rev):

printf "%s" {z..a}
\$\endgroup\$
1
  • \$\begingroup\$ Why not echo {a..z} ? \$\endgroup\$
    – Nate T
    Nov 20 '21 at 5:12
0
\$\begingroup\$

Know grep options

See also man 1p grep and manual for GNU Grep.

grep -x foo is equivalent to grep '^foo$

This is POSIX-compatible.

Consider only input lines that use all characters in the line excluding the terminating <newline> to match an entire fixed string or regular expression to be matching lines.

For example, the following two are equal:

grep -E '^abc$|^def$'
grep -F -x 'abc
def'
\$\endgroup\$
-1
\$\begingroup\$

ONE-LINE CONDITIONAL SETTER

NOTE: I see now that this first bit is already present on the first page, so I updated to add another tip (below the phrase 'EDIT-POINT:') which adds to the initial syntax to achieve a different outcome.

Here is a super efficient way to do control statements in bash.

instead of

if [ -e "${var}" ]
then
   echo "${var}"
else
   echo "empty"
fi

which translates to the one-liner:

if [ -e "${var}" ]; then echo "${var}"; else echo "empty"; fi

...you can use logical and / or like so:

[ -e "${var}" ] && echo "${var}" || echo "empty"

The second bit runs only if the first bit returns a 0 exit code. If either of the first two bits fail to exit with 0 (in other words, if false when used with [] / test command), the third bit runs. This means that in production / distribution use cases, this form is often a bad idea (if the if logic runs but fails fails, the else statement runs..), but for code golf, it is ore than sufficient, so long as you test it on the command line first.

It also works with the if / && (no else / ||), e.g.

[ -e "${var}" ] && echo "${var}"

if that is what you need. This version runs exactly as the long form, without the caveat mentioned above.

EDIT-POINT:

Another advantage of using this syntax is that you can put the entire command inside a variable. When you need to set a variable to a value that is based on a condition, this saves a lot of space.

For example,

if [ -e "${var}" ]
then
   TEXT="${var}"
else
    TEXT="empty"
fi

can be dropped to:

TEXT=$([ -e "${var}" ] && echo "${var}" || echo "empty")

As a code golf tip, this has limited uses, but they do exist. The space gained or lost depends on the size of the variable name. It adds up to the same as setting a four-letter variable (so ends in unity in the example above.)

Since we use single-letter variable names in code golf, this normally doesn't save any space at all. The space saving effect kicks in when we have no control over the name, in cases where we are using an environment variable in a cheeky way, or when a variable name is provided via a constraint in the question.

\$\endgroup\$
4
  • \$\begingroup\$ -1 because duplicated. \$\endgroup\$
    – user100411
    Nov 20 '21 at 9:00
  • 1
    \$\begingroup\$ @nrgmsbki4spot1 Thank you for commenting. I didnt even notice there were 2 pages. Deleting, but I will wait a few hours so you have time to see comment / thank you. I know thank yous are looked down upon in some of the sites, but for me, they are my favorite part, and honestly, they are what keeps me coming back. On that note, thanks again. \$\endgroup\$
    – Nate T
    Nov 21 '21 at 0:43
  • \$\begingroup\$ @nrgmsbki4spot1 Decided to edit instead. Added Another 'trick' which builds on the original. I left the original bit, as the new one wouldn't make much sense without it. Is this better? \$\endgroup\$
    – Nate T
    Nov 21 '21 at 6:06
  • 1
    \$\begingroup\$ As the first tip was previously included, I would assume that I am okay in the way of "one tip per answer", and I do feel it adds value. Sorry to bother you again. I am new to this SE, so I am still learning the sites nuances. Btw, if your wondering how someone could miss the first page altogether, its because I found it via link. \$\endgroup\$
    – Nate T
    Nov 21 '21 at 6:14
1
2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.