Skip to content

Instantly share code, notes, and snippets.

@jyi2ya
Created December 28, 2023 13:37
Show Gist options
  • Select an option

  • Save jyi2ya/89cdc4f61bf6e7a79642666fbb89ad75 to your computer and use it in GitHub Desktop.

Select an option

Save jyi2ya/89cdc4f61bf6e7a79642666fbb89ad75 to your computer and use it in GitHub Desktop.
看看你 2023 年使用终端的统计情况
#!/usr/bin/env perl
use 5.020;
use utf8;
use warnings;
use autodie;
use feature qw/signatures postderef/;
no warnings qw/experimental::postderef/;
use open qw(:std :utf8);
use DateTime;
use List::Util qw/uniq reduce pairs max/;
use DateTime::Duration;
use Text::ParseWords;
my $local_time_zone = DateTime::TimeZone->new(name => 'local');
sub parse_zsh {
my $history_file = $ENV{HOME} . '/.zsh_history';
open my $fd, '<', $history_file;
my @result;
while (<$fd>) {
my ($timestamp, $command_line) = /\S+\s+(\d+):[^;]+;(.*)$/;
unless (defined $timestamp and defined $command_line) {
warn sprintf "failed to parse history file at line %d: read $_", $fd->input_line_number;
next;
}
while ($command_line =~ /\\\\$/) {
$command_line .= <$fd>;
}
push @result, {
timestamp => $timestamp,
command_line => $command_line,
};
}
@result
}
sub parse_bash {
my $history_file = $ENV{HOME} . '/.bash_history';
# use File::HomeDir;
# File::HomeDir::my_home . '/.bash_history';
open my $fd, '<', $history_file;
my @result;
my $state = {
tag => 'expect_timestamp',
data => undef,
};
while (<$fd>) {
if ($state->{tag} eq 'expect_timestamp') {
if (/^#(\d+)$/) {
$state = {
tag => 'expect_command',
data => $1,
}
} else {
warn sprintf "failed to parse history file: expect a timestamp at line %d, read %s",
$fd->input_line_number, $_;
}
} elsif ($state->{tag} eq 'expect_command') {
push @result, {
timestamp => $state->{data},
command_line => $_,
};
$state = {
tag => 'expect_timestamp',
data => undef,
};
} else {
die "invalid state $state->{tag}";
}
}
@result
}
my @history_raw = (parse_zsh, parse_bash);
my @history = map {
my $date_time = DateTime->from_epoch(
epoch => $_->{timestamp},
time_zone => $local_time_zone,
);
my $date_time_shifted = $date_time - DateTime::Duration->new( hours => 6 );
my @fragments = eval { shellwords $_->{command_line} };
{
date_time => $date_time,
date_time_shifted => $date_time_shifted,
fragments => \@fragments,
}
} sort { $a->{timestamp} <=> $b->{timestamp} } @history_raw;
my $now = DateTime->now;
my $now_shifted = $now - DateTime::Duration->new( hours => 6 );
my $year = 2023;
my $first_command = $history[0];
say '';
say '=======';
say sprintf '记录中最早的命令出现在 %s,是 %d 天前',
$first_command->{date_time_shifted}->ymd('-'),
$now_shifted->delta_days($first_command->{date_time_shifted})->in_units('days');
my @history_this_year = grep { $_->{date_time_shifted}->year == $year } @history;
my @history_last_year = grep { $_->{date_time_shifted}->year == $year - 1 } @history;
my $active_days_this_year = uniq sort { $a cmp $b }
map { $_->{date_time_shifted}->ymd } @history_this_year;
my $active_days_last_year = uniq sort { $a cmp $b }
map { $_->{date_time_shifted}->ymd } @history_last_year;
say sprintf '今年活跃天数 %d,去年活跃天数 %d',
$active_days_this_year, $active_days_last_year;
my $command_num_this_year = int @history_this_year;
my $command_num_last_year = int @history_last_year;
say sprintf '今年命令数量 %d,去年命令数量 %d',
$active_days_this_year, $active_days_last_year;
my %commands_of_days = do {
my %result;
for my $command (@history_this_year) {
my $date = $command->{date_time_shifted}->ymd;
$result{$date} //= [];
push @{$result{$date}}, $command;
}
%result
};
my $top_day = reduce {
@{$commands_of_days{$a}} > @{$commands_of_days{$b}} ?
$a : $b,
} keys %commands_of_days;
say sprintf '使用命令最多的一天是 %s,当天运行了 %d 条命令。',
$top_day, int(@{$commands_of_days{$top_day}});
my %commands_of_months = do {
my %result;
for my $command (@history_this_year) {
my $date = $command->{date_time_shifted}->month;
$result{$date} //= [];
push @{$result{$date}}, $command;
}
%result
};
my $top_month = reduce {
@{$commands_of_months{$a}} > @{$commands_of_months{$b}} ?
$a : $b,
} keys %commands_of_months;
my $top_month_count = @{$commands_of_months{$top_month}};
my $terminal_width = 65;
say '';
say '=======';
say '今年命令使用情况如下:';
for my $month (sort { $a <=> $b } keys %commands_of_months) {
my $count = @{$commands_of_months{$month}};
printf '%02d ', $month;
printf '#' x int($terminal_width * $count / $top_month_count);
say "| $count";
}
my %bin_count;
for my $command (@history_this_year) {
my $bin = $command->{fragments}->[0];
next unless defined $bin;
$bin_count{$bin} //= 0;
$bin_count{$bin} += 1;
}
my @bin_count = reverse sort { $a->[1] <=> $b->[1] }
pairs %bin_count;
say '';
say '=======';
say '最爱的命令:';
for (1 .. 5) {
my ($bin, $count) = @{$bin_count[$_ - 1]};
say "$_: $bin($count 次)";
}
my %postfix_count;
for my $command (@history_this_year) {
for (@{$command->{fragments}}) {
my ($postfix) = m/\.([^.]+)$/g;
next unless defined $postfix;
$postfix_count{$postfix} //= 0;
$postfix_count{$postfix} += 1;
}
}
my @postfix_count = reverse sort { $a->[1] <=> $b->[1] }
pairs %postfix_count;
say '';
say '=======';
say '折腾最多的文件格式:';
for (1 .. 5) {
my ($postfix, $count) = @{$postfix_count[$_ - 1]};
say "$_: $postfix($count 次)";
}
sub seconds_of_day($dt) {
$dt->hour * 3600 + $dt->minute * 60 + $dt->second;
}
my @timespan_of_days =
map {
my @sorted = sort { $a->{date_time_shifted} <=> $b->{date_time_shifted} } @{$_->[1]};
[ $_->[0], $sorted[0], $sorted[$#sorted] ],
} pairs %commands_of_days;
my %commands_per_hour;
for (@history_this_year) {
my $hour = $_->{date_time}->hour;
$commands_per_hour{$hour} //= 0;
$commands_per_hour{$hour} += 1;
}
my $top_hour_count = max values %commands_per_hour;
say '';
say '=======';
say '各个时间段活跃程度';
for (sort { $a <=> $b } keys %commands_per_hour) {
my $count = $commands_per_hour{$_};
printf '%02d: ', $_;
printf '#' x int($terminal_width * $count / $top_hour_count);
printf "| %d\n", $count;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment