Created
December 28, 2023 13:37
-
-
Save jyi2ya/89cdc4f61bf6e7a79642666fbb89ad75 to your computer and use it in GitHub Desktop.
看看你 2023 年使用终端的统计情况
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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