I would like to get some performance improvement suggestions to a simple project I made using Bash in Linux.
The target is to read all the *.desktop
files, and extract Name
, Exec
, Icon
and Comment
entries. Then will be displayed in a GTK yad List.
I have built the code in two versions. Both versions works OK, but are very slow.
Version 1 :
Read all the files one by one & grep
the fields.
This version need about 9 seconds to read & grep
300 desktop files.
TimeStarted=$(date +%s.%N)
files=/usr/share/applications/*.desktop
fileindex=1
for i in $(ls $files); do
readarray -t executable < <(grep -m 1 "^Exec=" $i |cut -f 2 -d '=')
readarray -t comment < <(grep -m 1 "^Comment=" $i |cut -f 2 -d '=')
readarray -t comment2 < <(grep -m 1 "^GenericName=" $i |cut -f 2 -d '=')
readarray -t mname < <(grep -m 1 "^Name=" $i |cut -f 2 -d '=')
readarray -t icon < <(grep -m 1 "^Icon=" $i |cut -f 2 -d '=')
if [[ $comment = "" ]]; then
comment=$comment2
fi
yadlist+=( "$fileindex" "${icon[0]}" "${mname[0]}" "$i" "${executable[0]}" "${comment[0]}" ) #this sets double quotes in each variable.
fileindex=$(($fileindex+1))
done
TimeFinished=$(date +%s.%N); TimeDiff=$(echo "$TimeFinished - $TimeStarted" | bc -l)
Version 2:
grep
all the files at once for required fields.
This version improves the performance of the script, and needs 2 seconds to grep
300 desktop files.
TimeStarted=$(date +%s.%N)
files=/usr/share/applications/*.desktop
i=$files
fileindex=1
IFS=$'\n'
readarray -t fi < <(printf '%s\n' $i)
readarray -t executable < <(grep -m 1 '^Exec=' $i)
readarray -t noexecutable < <(grep -L '^Exec=' $i)
readarray -t comment < <(grep -m 1 "^Comment=" $i )
readarray -t nocomment < <(grep -L "^Comment=" $i )
readarray -t comment2 < <(grep -m 1 "^GenericName=" $i )
readarray -t nocomment2 < <(grep -L "^GenericName=" $i )
readarray -t mname < <(grep -m 1 "^Name=" $i )
readarray -t nomname < <(grep -L "^Name=" $i )
readarray -t icon < <(grep -m 1 "^Icon=" $i )
readarray -t noicon < <(grep -L "^Icon=" $i )
for items1 in ${noexecutable[@]}; do
executable+=($(echo "$items1"":Exec=None"))
done
for items2 in ${nocomment[@]}; do
comment+=($(echo "$items2"":Comment=None"))
done
for items3 in ${nocomment2[@]}; do
comment2+=($(echo "$items3"":GenericName=None"))
done
for items4 in ${nomname[@]}; do
mname+=($(echo "$items4"":Name=None"))
done
for items5 in ${noicon[@]}; do
icon+=($(echo "$items5"":Icon=None"))
done
sortexecutable=($(sort <<<"${executable[*]}"))
sortcomment=($(sort <<<"${comment[*]}"))
sortcomment2=($(sort <<<"${comment2[*]}"))
sortmname=($(sort <<<"${mname[*]}"))
sorticon=($(sort <<<"${icon[*]}"))
trimexecutable=($(grep -Po '(?<=Exec=)[ --0-9A-Za-z/]*' <<<"${sortexecutable[*]}"))
trimcomment=($(grep -Po '(?<=Comment=)[ --0-9A-Za-z/]*' <<<"${sortcomment[*]}"))
trimcomment2=($(grep -Po '(?<=GenericName=)[ --0-9A-Za-z/]*' <<<"${sortcomment2[*]}"))
trimmname=($(grep -Po '(?<=Name=)[ --0-9A-Za-z/]*' <<<"${sortmname[*]}"))
trimicon=($(grep -Po '(?<=Icon=)[ --0-9A-Za-z/]*' <<<"${sorticon[*]}"))
unset IFS
ae=0
for aeitem in ${fi[@]};do
if [[ ${trimcomment[ae]} = "None" ]]; then
trimcomment[ae]=${trimcomment2[ae]}
fi
yadlist+=( "$fileindex" "${trimicon[$ae]}" "${trimmname[$ae]}" "${fi[$ae]}" "${trimexecutable[$ae]}" "${trimcomment[$ae]}" ) #this sets double quotes in each variable.
fileindex=$(($fileindex+1))
ae=$(($ae+1))
done
TimeFinished=$(date +%s.%N); TimeDiff=$(echo "$TimeFinished - $TimeStarted" | bc -l)
Remarks:
a) Some .dekstop
files do not include all the required fields.
b) Performance refers to 64-bit Intel Celeron N3050 - 4GB ram machine, running 64bit Debian 8 Sid with XFCE and GNU bash 4.4.0(1) and GNU grep
2.26. PS: Performance of 9 or 2 seconds is also verified by time ./script.sh
.
c) The version 2 script performance can achieve below 0.5 seconds if I remove the "for" sections, but then yadlist becomes a chaos due to the missing fields in some .desktop
files.
Result:
According to my opinion, even 2 seconds to grep
300 files it is still too much time for such a small number of files.
Is it possible to further optimize this scripts performance in Bash?
As a sample , you can have a look at this caja.desktop
file, taken from my system. Notice that Comment
entry is missing.
[Desktop Entry]
Name=Caja
Name[af]=Caja
<More Name entries for different locale>
GenericName=File Manager
GenericName[af]=Lêerbestuurder
<more GenericName entries for different locale>
Exec=caja
Icon=system-file-manager
Terminal=false
Type=Application
StartupNotify=true
NoDisplay=true
OnlyShowIn=MATE;
In other .desktop
files, the comment entry (if present) looks like this:
Comment=View multi-page documents
<various Comment entries for different locale>
.desktop
file ? \$\endgroup\$