In bash I have the following array with data:
(
"01.05.2022 01:01:35 [ERROR] test
error message"
"01.05.2022 00:00:55 [SUCCESS] test
success message"
"02.05.2022 00:00:35 [ERROR] test
error message"
"01.05.2022 00:05:00 [WARNING] test
warning message"
)
Expected Result:
(
"01.05.2022 00:00:55 [SUCCESS] test
success message"
"01.05.2022 00:05:00 [WARNING] test
warning message"
"01.05.2022 01:01:35 [ERROR] test
error message"
"02.05.2022 00:00:35 [ERROR] test
error message"
)
How can I sort a given array by dates only directly in bash and save it to a file?
CodePudding user response:
How can I sort a given array by dates only directly in bash and save it to a file?
I’m assuming that “only directly in Bash” implies that additional processes and external tools such as sort
are disallowed.
A solution in pure Bash can be based around the fact that indexed “arrays” in Bash are sparse and automatically sorted by (possibly sparse) indices.
That said, the problem translates into a choice of sparse indices such that their sorting order corresponds to the sorting order of dates. For example, one can pick indices in which four highest decimal orders represent years, lowest two decimal orders represent seconds and everything else is in between, rather obviously:
sort_my_special_array() {
local -n array="$1" # passing array by reference
local -a reindexed # sparse array indexed by YYYYMMDDhhmmss
local -i idx # index in the above
local a year month day hour minute second drop
for a in "${array[@]}"; do
IFS='.: ' read day month year hour minute second drop <<< "$a"
idx="10#${year}${month}${day}${hour}${minute}${second}"
reindexed[idx]="$a"
done
array=("${reindexed[@]}")
}
A simple test for the code above:
my_bash_array=(
'01.05.2022 01:01:35 [ERROR] test
error message'
'01.05.2022 00:00:55 [SUCCESS] test
success message'
'02.05.2022 00:00:35 [ERROR] test
error message'
'01.05.2022 00:05:00 [WARNING] test
warning message'
)
echo -e 'Before:'
(IFS=$'\n'; echo "${my_bash_array[*]@Q}";)
sort_my_special_array my_bash_array # Here!
echo -e '\nAfter:'
(IFS=$'\n'; echo "${my_bash_array[*]@Q}";)
The output of the test is (…drumroll…):
Before:
$'01.05.2022 01:01:35 [ERROR] test\n error message'
$'01.05.2022 00:00:55 [SUCCESS] test\n success message'
$'02.05.2022 00:00:35 [ERROR] test\n error message'
$'01.05.2022 00:05:00 [WARNING] test\n warning message'
After:
$'01.05.2022 00:00:55 [SUCCESS] test\n success message'
$'01.05.2022 00:05:00 [WARNING] test\n warning message'
$'01.05.2022 01:01:35 [ERROR] test\n error message'
$'02.05.2022 00:00:35 [ERROR] test\n error message'
To store a Bash array into a file in a parsable format, you can use the A
modifier:
echo "${my_bash_array[@]@A}" > /path/to/some/file
This↑↑↑ yields the following output:
declare -a my_bash_array=([0]=$'01.05.2022 00:00:55 [SUCCESS] test\n success message' [1]=$'01.05.2022 00:05:00 [WARNING] test\n warning message' [2]=$'01.05.2022 01:01:35 [ERROR] test\n error message' [3]=$'02.05.2022 00:00:35 [ERROR] test\n error message')
CodePudding user response:
Your dates appear at the start of each string and are in a format that allows to sort them alphabetically (i.e. without the need of conversion to EPOCH time).
update: I was wrong. As @markp-fuso noticed, the dates are not in sortable format so you'll have to decorerate | sort | undecorate
them
So, given the bash array:
my_bash_array=(
"01.05.2022 01:01:35 [ERROR] test
error message"
"01.05.2022 00:00:55 [SUCCESS] test
success message"
"02.05.2022 00:00:35 [ERROR] test
error message"
"01.05.2022 00:05:00 [WARNING] test
warning message"
)
Here's a simple way to sort this array by date (that is, if your sort
command supports the -z
option):
# for bash >= 4.3:
readarray -d '' my_bash_array < <(
printf '%s\0' "${my_bash_array[@]}" |
sed -z -E 's/^((..)\.(..)\.(....) (..):(..):(..) )/\4\3\2\5\6\7 \1/' |
sort -z -n -k1,1 |
sed -z 's/^[^ ]* //'
)
# for older bash:
i=0
while IFS='' read -r -d '' element
do
my_bash_array[i ]=$element
done < <(
printf '%s\0' "${my_bash_array[@]}" |
sed -z -nE 's/^((..)\.(..)\.(....) (..):(..):(..) )/\4\3\2\5\6\7 \1/p' |
sort -z -n -k1,1 |
sed -z 's/^[^ ]* //'
)