HackerRank Shell¶
Intro¶
Unless otherwise noted, assume all scripts contain the following shebang:
#!/usr/bin/env bash
Easy Challenges¶
https://www.hackerrank.com/domains/shell
Let’s Echo¶
Tags: #cmdline #shell #bash #echo #printf Links: challenge
$ echo HELLO
$ printf '%s\n' HELLO
Looping With Numbers¶
Tags: #cmdline #shell #bash #numbers #looping #for
Links: challenge
for (( i = 1; i <= 9; ++i ))
do
echo "$i"
done
Or using ranges:
$ printf '%d\n' {1..50}
Looping And Skipping¶
Tags: #cmdline, #numbers #looping #for
Links: challenge
for (( i = 1; i <= 9; ++i ))
do
if (( i % 2 == 0 ))
then
continue
fi
echo "$i"
done
$ bash script.sh
1
3
5
7
9
Could also use ‘’echo:’’
$ echo -ne {1..9..2} '\n'
The -e
option is to enable some escapes. help echo
for more.
Or using seq
:
$ seq -s ' ' 1 2 9
A Personalized Echo¶
Tags: #cmdline #read #echo
Links: challenge
$ read -r name
$ printf 'Welcome %s\n' "$name"
The World of Numbers¶
Tags: #cmdline #shell #bash #numbers #math #bc #ranges
Links: challenge
First, see this clever use of range to produce the math expressions:
$ read -r x y
8 2
$ printf '%s\n' "$x"{+,-,*,/}"$y"
8+2
8-2
8*2
8/2
Then, feed those expressions to bc
:
$ read -r x y
8 2
$ printf '%s\n' "scale=2; $x"{+,-,*,/}"$y" | bc
10
6
16
4.00
If y
is negative, like -2
we would receive an error:
$ read -r x y
5 -2
$ printf '%s\n' "scale=2; $x"{+,-,*,/}"$y" | bc
3
(standard_in) 2: syntax error
-10
-2.50
Adding parenthesis prevents the error, because our expression would be
like 5--2
, but 5-(-2)
is OK with bc
:
$ read -r x y
5 -2
$ printf '%s\n' "scale=2; $x"{+,-,*,/}"($y)" | bc
3
7
-10
-2.50
Or something more manual and verbose:
read x </dev/stdin
read y </dev/stdin
printf '%d\n' $(( x + y ))
printf '%d\n' $(( x - y ))
printf '%d\n' $(( x * y ))
printf '%d\n' $(( x / y ))
NOTE: The challenge wants integer division, so, we simply omit
bc
’s scale special variable.
read -r answer
case "$answer" in
[Yy]*)
printf '%s\n' YES
;;
[Nn]*)
printf '%s\n' NO
;;
*)
printf '%s\n' 'What the poop‽ 💩'
;;
esac
$ bash script.sh
yes
YES
$ bash script.sh
Y
YES
$ bash script.sh
n
NO
$ bash script.sh
lol
What the poop‽ 💩
Getting started with conditionals¶
Tags: #cmdline #shell #bash #conditionals
Links: challenge
read -r answer
case "$answer" in
[Yy]*)
printf '%s\n' YES
;;
[Nn]*)
printf '%s\n' NO
;;
*)
printf '%s\n' 'What the poop‽ 💩'
;;
esac
$ bash script.sh
yes
YES
$ bash script.sh
Y
YES
$ bash script.sh
n
NO
$ bash script.sh
lol
What the poop‽ 💩
More on Conditionals¶
Tags: #cmdline #shell #bash #conditionals #math
Links: challenge
Solution based on side lengths.
equilateral: x == y && y == z
scalene: x != y && y != z && z != x
isosceles: any other
read -r x
read -r y
read -r z
[[ "$x" == "$y" ]] && [[ "$y" == "$z" ]] && echo EQUILATERAL && exit 0
[[ "$x" != "$y" ]] && [[ "$y" != "$z" ]] && [[ "$z" != "$x" ]] && echo SCALENE && exit 0
echo ISOSCELES && exit 0
Arithmetic Operations¶
Tags: #cmdline #shell #bash #math #bc
Links: challenge
expression="$1"
printf '%.3f\n' "$(echo "$expression" | bc -l)"
bc -l
produces up to 6 decimal places. If we use bc
scale to 3,
for instance, depending on the result, we would produce wrong results
because printf %f
format specifier does rounding by itself.
bc
scale is 0 by default if not explicitly set. Also, bc
does no
rounding.
printf
rounds up from 6, and down from 5:
$ printf '%.3f\n' 1.2583
1.258
$ printf '%.3f\n' 1.2585
1.258
$ printf '%.3f\n' 1.2586
1.259
Only when the number after 8 passes 5, that is, 6 and above, is that the
number is rounded up to 1.259. If one uses scale=3
in bc
, then
it truncates (does not round) to three decimal places and printf
has
no way to round up, making the solution to the exercise incorrect.
Therefore, we use bc -l
without scale, or use scale=4
at least.
Compute the Average¶
Tags: #cmdline #shell #bash #math
Links: challenge
read -r n
sum=0
if [[ "$n" == 0 ]]
then
printf '%.3f\n' "$(echo 'scale=4; 0' | bc -l)"
exit 0
fi
for ((i = 0; i < n; ++i))
do
read -r x
sum=$((sum + x))
done
printf '%.3f\n' "$(echo "scale=4; $sum / $n" | bc -l)"
We used scale=4
by the same reasons described earlier about
truncating and rounding.
cut Challenges¶
Tags: #cmdline #shell #bash #cut
$ cut -b 3 -
$ cut -b 2,7 -
$ cut -b 2-7 -
$ cut -b 1-4 -
$ cut -d $'\t' -f 1,2,3 -
$ cut -c 13- -
$ cut -d ' ' -f 4 -
$ cut -d ' ' -f 1,2,3 -
$ cut -d $'\t' -f 2- -
Head of Text File Challenges¶
$ head -n 20
$ head -c 20
Middle of a Text File¶
Tags: #cmdline #shell #bash #sed
Links: challenge
$ sed -n '12,22 p'
Tail of a Text File 1 and 2¶
Tags: #cmdline #shell #bash #tail
Links: challenge
$ tail -n 20 -
$ tail -c 20 -
tr Command 1¶
Tags: #cmdline #shell #bash #tr #here-document #assignment
Links: challenge
# Assign some text to the variable `input'.
$ read -r -d '' input << 'EOF'
int i = (int) 5.8;
int res = (23 + i) * 2;
EOF
# Inspect `input' contents.
$ echo "$input"
int i = (int) 5.8;
int res = (23 + i) * 2;
# Apply `tr' to `input' and see ( and ) replaced with [ and ].
$ echo "$input" | tr '()' '[]'
int i = [int] 5.8;
int res = [23 + i] * 2;
A Here
Document
is used to assign lines of text to the variable input
.
tr Command 2¶
Tags: #cmdline #shell #bash #tr
Links: challenge
$ tr -d 'a-z'
tr Command 3¶
Tags: #cmdline #shell #bash #tr
Links: challenge
$ tr -s ' '
sort Lines Challenges¶
Tags: #cmdline #shell #bash #sort
Links: challenge
$ echo -e 'aa\nbb\naa\ncc\nff\ncc' | sort -
aa
aa
bb
cc
cc
ff
$ echo -e 'aa\nbb\naa\ncc\nff\ncc' | sort -r -
ff
cc
cc
bb
aa
aa
$ echo -e '2.1\n3\n0.2\n0' | sort -n -
0
0.2
2.1
3
$ echo -e '2.1\n3\n0.2\n0' | sort -nr -
3
2.1
0.2
0
# Sort by field 2, taking Tab as field separator.
$ sort -t $'\t' -nr -k 2 -
# Same, but in ascending order.
$ sort -t $'\t' -n -k 2 -
# This time the delimiter is a “|” character
$ sort -t '|' -nr -k 2 -
uniq Challenges¶
Tags: #cmdline #shell #bash #uniq
Links: challenge
$ uniq -
```
Display the count of lines that were uniqfied and the uniqfied lines without leading whitespace/tabs:
$ read -r -d '' lines << 'EOF'
> foo
> foo
> bar
> bar
> bar
> tux
> EOF
$ echo "$lines" | uniq -c - | sed 's/ \+\([0-9]\+ [^ ]\+\)/\1/'
2 foo
3 bar
1 tux
$ echo "$lines" | uniq -c - | sed 's/^[[:space:]]*//g'
2 foo
3 bar
1 tux
$ echo "$lines" | uniq -c - | cut -b 7- -
2 foo
3 bar
1 tux
$ echo "$lines" | uniq -c - | xargs -l
2 foo
3 bar
1 tux
$ echo "$lines" | uniq -c - | xargs -L 1
2 foo
3 bar
1 tux
$ echo "$lines" | uniq -c - | colrm 1 6
2 foo
3 bar
1 tux
# Case Insenstivie.
$ read -r -d '' lines << 'EOF'
> FoO
> fOO
> baR
> Bar
> bAr
> TUX
> EOF
$ echo "$lines" | uniq -ci - | cut -b 7- -
2 FoO
3 baR
1 TUX
$ echo "$lines" | uniq -u -
TUX
Read In An Array¶
Tags: #cmdline #shell #bash #arrays
Links: challenge
$ arr=()
$ while read -r line ; do arr+=("$line") ; done < /dev/stdin
$ echo "${#arr[*]}"
Display an Element of an Array¶
Tags: #cmdline #shell #bash #arrays
Links: challenge
mapfile -t countries
echo "${countries[3]}"
-t
in mapfile
removes the trailing delimiter so the array
elements are “clean”.
Count Elements in an Array¶
Tags: #cmdline #shell #bash #arrays
Links: challenge
mapfile -t countries
echo "${#countries[@]}"
Slice An Array¶
Tags: #cmdline #shell #bash #arrays
Links: challenge
Print the array with the syntax ${arr[*]:OFFSET:LENGTH}
.
$ read -r -d '' countries << 'EOF'
> Namibia
> Nauru
> Nepal
> Netherlands
> NewZealand
> Nicaragua
> Niger
> Nigeria
> NorthKorea
> Norway
> EOF
$ echo "${arr[*]:3:5}"
Netherlands NewZealand Nicaragua Niger Nigeria NorthKorea Norway
Could read with countries=($(cat))
too, but ShellSheck complains.
Either use the read
as above, or with mapfile -t arr
.
Other options would be:
paste -d ' ' -s | cut -d ' ' -f4-8 -
and:
head -8 | tail -5 | paste -s -d ' ' -
Concatenate Array With Itself¶
Tags: #cmdline #shell #bash #arrays
Links: challenge
mapfile -t countries
countries+=("${countries[@]}" "${countries[@]}")
echo "${countries[*]}"
grep A¶
Tags: #cmdline #shell #sed
Links: challenge
$ grep -iw 'th\(e\|at\|en\|ose\)'
grep B¶
Tags: #cmdline #shell #grep
Links: challenge
Works locally but not on HackerRank:
$ grep '\(.\) \?\1'
This works locally and on HackerRank:
$ grep '\(.\) \?\1'
sed 3¶
Tags: #cmdline #shell #sed
Links: challenge
$ sed 's/[Tt][Hh][Yy]/{&}/g'
sed 4¶
Tags: #cmdline #shell #sed
Links: challenge
$ sed 's/.* \([0-9]\{4\}\)/**** **** **** \1/g'
Or
$ sed 's/[0-9]\+ /**** /g'
Medium Challenges¶
Paste 1¶
Tags: #cmdline #shell #paste
Links: challenge
$ paste -s -d ';' -
paste 2¶
Tags: #cmdline #shell #paste
paste -d ';' - - -
paste 3¶
Tags: #cmdline #shell #paste
Links: challenge
$ paste -s -
paste 4¶
Tags: #cmdline #shell #paste
Links: challenge
$ paste - - -
sed 1¶
Tags: #cmdline #shell #sed
Links: challenge
$ sed 's/\<the\>/this/'
sed 2¶
Tags: #cmdline #shell #sed
Links: challenge
grep challenges¶
Tags: #cmdline #shell #grep
Links: challenge1, challenge2, challenge3
$ grep '\<the\>'
$ grep -i '\<the\>'
$ grep -iv '\<that\>'
awk challenges¶
Tags: #cmdline #shell #awk
Links: challenge 1, challenge 2, challenge 3, challenge 4
Challenge 1:
$ awk '{ if ($4 == "") print "Not all scores are available for " $1 }'
Challenge 2:
awk '{
answer[0] = "Fail";
answer[1] = "Pass";
print $1, ":", answer[$2 >= 50 && $3 >= 50 && $4 >= 50];
}'
Challenge 3:
awk '{
avg=($2 + $3 + $4) / 3
if (avg >= 80)
print $0 " : A";
else if (avg >= 60)
print $0 " : B";
else
print $0 " : FAIL";
}'
Challenge 4:
awk 'ORS=NR % 2 ? ";" : "\n"'
Filter an Array With Patterns¶
Tags: #cmdline #shell #bash #arrays #pattern-matching
Links: challenge
while read -r line ; do
if [[ ! "$line" =~ [Aa] ]]
then
echo "$line"
fi
done
Remove First Capital Letter From Each Array Element¶
Tags: #cmdline #shell #bash #arrays #pattern-matching
Links: challenge
arr=()
while read -r line ; do
arr+=("${line/[A-Z]/.}")
done
echo "${arr[*]}"
Hard Challenges¶
sed 5¶
Tags: #cmdline #shell #sed
Links: challenge
sed 's/\([0-9]\+\) \([0-9]\+\) \([0-9]\+\) \([0-9]\+\)/\4 \3 \2 \1/'
NOTE: Backreferences in the search pattern mean they match the same
chars, not the same general regex. That is, (.)o(.)
matches “bob” or
“bob”, for instance, but not “bop”. If (.)
matched “x”, then \1
in the search must also match an “x”. That is why we can’t do
s/\([0-9]\+\) \1 \1 \1
, because it would only match if all four
fields of the number were the same thing, like “1234 1234 1234 1234”.
Lonely Integer¶
Tags: #cmdline #shell #bash #numbers
Links: challenge
Not very elegant, but makes use of arrays, which is what they ask for.
#!/usr/bin/env bash
#
# This solution uses a histogram-like approach.
#
# Dummy-read, since we don't need the first argument they
# feed into the input.
read -r
# Read input numbers.
read -r -a nums
# An array to keep track of which numbers appeared how many times.
declare -A hist
for n in "${nums[@]}"
do
if [[ -z "${hist[$n]}" ]]
then
# Use the number as index and increment that index and
# initialize it to 1.
hist[$n]=1
else
# Increment it each time that number appears.
hist[$n]=$((${hist[$n]} + 1))
fi
done
# Iterate over the indexes.
for idx in "${!hist[@]}"
do
# If that number appeared only once...
if (( hist[$idx] == 1 ))
then
# ...then print it and bail out.
echo "$idx"
break;
fi
done
Fractal Tree¶
Tags: #cmdline #shell #bash
Links: challenge
#!/usr/bin/env bash
#
# Invoke it like this:
#
# bash script.sh 5
#
declare -A grid
rows=63
cols=100
#
# Initialize the 63x100 grid with underscores.
#
init () {
for (( row = 0; row < rows; ++row ))
do
for (( col = 0; col < cols; ++col ))
do
grid[$row,$col]=_
done
done
}
#
# Actually treeify the drawing.
#
treeify () {
local count=$1
local row=$2
local col=$3
local iteration=$4
for (( i = 0; i < count; ++i ))
do
grid[$row,$col]=1
(( row -= 1 ))
done
for (( i = 0; i < count; i++ ))
do
grid[$row,$((col - i - 1))]=1
grid[$row,$((col + i + 1))]=1
(( row -= 1 ))
done
if (( iteration > 1 ))
then
treeify $(( count >> 1 )) "$row" $(( col - count )) $(( iteration - 1 ))
treeify $(( count >> 1 )) "$row" $(( col + count )) $(( iteration - 1 ))
fi
}
#
# Simply output the grid, already treeified, to the screen.
#
display () {
for (( row = 0 ; row < rows ; ++row ))
do
for (( col = 0 ; col < cols ; ++col ))
do
printf '%s' "${grid[$row,$col]}"
done
printf '\n'
done
}
initial_count=16
initial_row=62
initial_col=49
iterations="${1:-5}"
if (( 1 > iterations || iterations > 5 ))
then
printf '%s\n' 'Provide a number between 1 and 5, please.' 1>&2
else
init
treeify "$initial_count" "$initial_row" "$initial_col" "$iterations"
display
fi