是否将50000封选定的电子邮件(不是全部)从同一服务器上的一个IMAP文件夹移动到另一个文件夹



我使用的IMAP邮件文件夹包含80000封邮件,我想将选定的约50000封邮件的子集(所有日期为2020年的邮件(移动到同一IMAP服务器上的另一个文件夹。这可以从客户端计算机(而不是IMAP服务器计算机(上的Linux命令行编写脚本吗?获取邮件?移动邮件?任何东西

删除也有同样的问题:有没有办法通过Linux命令行工具在IMAP邮件文件夹中选择(比如(2018年的50000封电子邮件并将其删除?

注意:如果重要的话,服务器是outlook.com(Office 365(。我尝试使用Outlook(GUI(进行此操作,但它甚至不能选择"来自年度的所有电子邮件";以任何有效的方式。我还尝试了Thunderbird with Owl说Exchange,尽管我可以选择电子邮件,但这一重大举措根本没有发生。脚本方法是最新的尝试。

谢谢!

我最近发现自己不得不自动将IMAP服务器上的邮件从一个文件夹移动到另一个文件夹。

为了可靠地做到这一点,我编写了一个脚本,称之为imap-helper。它连接到IMAP服务器,并在两个文件夹之间移动与给定查询字符串匹配的邮件。它使用由我的发行版(Arch(中的包(perl-mail-imapclient(提供的Mail::IMAPClient

在我对原始帖子的评论中,我建议使用gnutls-cli连接到IMAP服务器;我仍然建议您这样做,以了解幕后发生的事情(这样您就可以更快地调试查询语法(。这里有一个示例的原始IMAP会话仅供参考,但它后面的脚本应该更适合您的问题。

  • (工具rlwrap为交互提供GNU Readline编辑和历史记录(

    $ rlwrap -S "> " gnutls-cli imap.mail.yahoo.com -p 993
    > A LOGIN myusername mypassword
    A OK LOGIN completed
    > A LIST "" *           # List all the folders on the server
    * LIST (HasNoChildren) "/" "ALL"
    * LIST (Junk HasNoChildren) "/" "Bulk Mail"
    * LIST (HasNoChildren) "/" "Inbox"
    ...
    A OK LIST completed
    > A SELECT Inbox
    * 12 EXISTS
    * 0 RECENT
    ...
    A OK [READ-WRITE] SELECT completed; now in selected state
    > A SEARCH BEFORE 01-Jan-2021
    * SEARCH 11 12
    A OK SEARCH completed
    > A MOVE 11,12 "Bulk Mail"
    * OK [COPYUID 1609256255 58:59 57:58]
    ...
    A OK MOVE completed
    

至于这个脚本,我已经在我的Gmail收件箱中测试过了,它有13000条消息。我把它们都从INBOX移到了INBOX2(Gmail自动创建的(。这花了几分钟时间。然后我又把它们移回来了。如果它能在你的服务器上运行,我很高兴。

对于您的情况,您要做的是首先创建一个名为~/.imap-creds.pl的文件,其中包含您的服务器、用户名和密码。

然后你会运行类似的程序

$ imap-helper outlook FOLDER1 'SINCE "01-Jan-2019" BEFORE "01-Jan-2020"' FOLDER2 -e

将2019年收到的FOLDER1中的所有消息移动到FOLDER2中。用户界面的设计使您可以一次构建一个参数的命令:

$ imap-helper         # lists accounts from config file
gmail
...
$ imap-helper gmail   # lists folders in the gmail account
INBOX
Personal
Receipts
...
$ imap-helper gmail INBOX   # lists messages in INBOX
...
etc.

这是脚本:

#!/usr/bin/perl
# 21 Jan 2021
use warnings;
use strict;
use open (":encoding(UTF-8)", ":std" );
use Mail::IMAPClient;
use Data::Dumper;
$Data::Dumper::Indent=0;
$Data::Dumper::Purity=1;
$Data::Dumper::Terse=1;
use Getopt::Long;
Getopt::Long::Configure ("bundling", "no_ignore_case");
use Carp;
$SIG{__DIE__} = sub {
my $error = shift;
Carp::confess "Error: ";
};
# change the help text if you change this
my $credfn=glob("~/.imap-creds.pl");
my($bad_args, $help, $verbose, $execute, $max, $zero_results_ok);
GetOptions('-h|help' => $help,
'-v|verbose' => $verbose,
'-e|execute' => $execute,
'-z|zero-results-ok' => $zero_results_ok,
'-m|max=f' => $max
) or $bad_args = 1;
my ($action);
my ($acct, $src, $dst, $query);
sub verb {
warn "imap-helper: ",@_,"n" if $verbose;
}
if(@ARGV>4) {
warn "Expected 4 arguments but you passed ".scalar(@ARGV);
$bad_args = 1;
} elsif(@ARGV==4) {
$action = "move";
} elsif(@ARGV==3) {
$action = "search";
} elsif(@ARGV==2) {
$action = "list";
} elsif(@ARGV==1) {
$action = "folders";
} elsif(@ARGV==0) {
$action = "accounts";
}
($acct,$src,$query,$dst) = @ARGV;
sub usage {
"Usage: imap-helper [-h | -v | -e | -z] ACCOUNT SRC_FOLDER QUERY DST_FOLDERn";
}
sub help {
q{
Options:
-h --help     print this message
-v --verbose  be verbose
-e --execute  execute the move
-z --empty-search-ok   exit 0 on empty search (for scripts)
By default no messages are moved, pass -e to execute the move.
Configuration: ~/.imap-creds.pl
Config syntax: [ SERVER_NAME => { Server => "HOST",
User     => "USER",
Password => "PASS"
}, ... ]
With zero arguments, lists accounts. With only the ACCOUNT argument,
lists folders on ACCOUNT. With SRC_FOLDER argument, list contents of
SRC_FOLDER. With QUERY argument, list results of QUERY. With
DST_FOLDER argument, plan a move of messages matching QUERY from
SRC_FOLDER to DST_FOLDER. Pass "-e" to execute the move.
QUERY is an IMAP query, like "ALL" or 'BEFORE "15-Jan-2021"' (dates must
be in this exact format). Other keywords include TO, CC, FROM,
SUBJECT, TEXT; AND, OR, NOT; LARGER, SMALLER; NEW, RECENT, SEEN,
ANSWERED. See <https://tools.ietf.org/html/rfc3501> for a full list.
IMAP queries can be combined, for example:
$ imap-helper gmail INBOX 'SINCE "01-Jan-2020" BEFORE "01-Jan-2021"'
# (lists all INBOX messages from 2020)
This tool uses MOVE which is not part of the original IMAP RFC but
which should be well supported.
Example interaction:
$ cat .imap-creds.pl
[ yahoo =>
{ Server   => 'imap.mail.yahoo.com',
User     => 'napoleon',
Password => 'MYPASSWORD123'
},
gmail => ...
]
$ imap-helper
yahoo
gmail
$ imap-helper yahoo
ALL
Archive
Inbox
...
$ imap-helper yahoo Inbox
48    19 Jan 2021  XXXXX@yahoo.com           YYYYY@gmail.com           test 2
...
$ imap-helper yahoo Inbox "BEFORE 15-Jan-2021"
50    29 Dec 2020  XXXXX@yahoo.com           YYYYY@yahoo.com           test 1
...
$ imap-helper yahoo Inbox "BEFORE 15-Jan-2021" Archive -e -v -z
imap-helper: Connecting to server imap.mail.yahoo.com as XXXXX
imap-helper: Searching for BEFORE 15-Jan-2021
imap-helper: Found 1 matches for BEFORE 15-Jan-2021
imap-helper: Moving 1 messages
};
}
if($bad_args) { print STDERR usage; exit(1) }
if($help) {
my $pager = $ENV{PAGER};;
open STDOUT, "| $pager" or warn "Not paging STDOUT: $!n" if defined $pager;
print (usage, help);
close(STDOUT); wait(); exit(0);
}
die "Shouldn't get here" if !defined $action;
################################################################
## LOGIN
if(!-e $credfn) {
die "Missing credential file $credfnn";
}
my $creds = eval `cat $credfn`;
#verb (Dumper($creds));
ref $creds eq "ARRAY" or die "Expected an array: $credfnn".
"Got: ".(Dumper($creds))."n";
if($action eq "accounts") {
# no account specified, just list them all
verb "Listing accounts from $credfnn";
my $ind = 0;
my @accts = grep {!($ind++ % 2)} (@$creds);
print "$_n" for(@accts);
exit(0);
}
my %creds = @$creds;
my $srvcr = $creds{$acct};
if(!defined $srvcr) {
die "Account $acct not found in $credfnn";
}
verb "Connecting to server $srvcr->{Server} as $srvcr->{User}";
my $imap = Mail::IMAPClient->new(
Server   => $srvcr->{Server},
User     => $srvcr->{User},
Password => $srvcr->{Password},
Ssl      => 1,
Uid      => 1,
) or die "Could not connect to $srvcr->{Server} as $srvcr->{User}n";
if($action eq "folders") {
my $folders = $imap->folders
or die "Error listing folders: ", $imap->LastError, "n";
print join("n",@$folders),"n";
exit(0);
}
$imap->select( $src )
or die "Select $src error: ", $imap->LastError, "n";
# Truncate a string $s to width $w, for use in a table
sub trunc {
my ($s, $w) = @_;
$s = "" if !defined($s);
if(ref $s eq "ARRAY") {
$s = join(",",@$s);
}
my $l = length($s);
my $o;
if($l>=$w) {
$o = substr($s,0,$w-3)."...";
} else {
$o = $s.(" "x($w-$l));
}
return $o;
}
sub show_msgs {
my @msgs = @_;
# this should but doesn't work (as first argument to parse_headers)
#  my $r = $imap->Range(@msgs);
my $heads = $imap->parse_headers(@msgs, "Date", "Subject", "To", "From");
#  for my $msg (sort {$a <=> $b} (keys %$heads)) {
for my $msg (@msgs) {
my $hs = $heads->{$msg};
my $date = $hs->{Date}->[0]||"";
# remove weekday and HH:MM:SS from date
$date =~ s/^D+,s*//;
$date =~ s/ dd:.*$//;
print trunc($msg,7)," ",
trunc($date,12)," ",
trunc($hs->{To},25)," ",
trunc($hs->{From},25)," ",
trunc($hs->{Subject},25),
"n";
}
}
if($action eq "list") {
my @msgs = $imap->messages;
if(defined($max) && @msgs>$max) { @msgs = @msgs[0..($max-1)]; }
show_msgs(@msgs);
exit(0);
}
verb "Searching for $query";
my @msgs = $imap->search($query);
if(!@msgs) {
if(!$zero_results_ok) {
die "Error or no matches for $query ",$imap->LastError,"n";
} else {
verb "No matches found for $query";
exit 0;
}
}
verb "Found ".(@msgs)." matches for $query";
if(defined $max && @msgs > $max) { @msgs = @msgs[0..($max-1)]; }
if($action eq "search") {
show_msgs(@msgs);
exit(0);
}
if($action eq "move") {
verb "Moving ".(@msgs)." messages";
my $msgstr=join(",",@msgs);
if(!$execute) {
warn "Would have moved $msgstr from $src to $dstn";
warn "Pass -e to execute moven";
} else {
$imap->move($dst,$msgstr)
or die "Could not move messagesn";
}
exit(0);
}
die "Something wrong";

相关内容

最新更新