根据文件名的一部分将文件移动到不同的文件夹

时间:2017-08-01 18:44:46

标签: powershell

我正在寻找一个脚本,根据文件名的一部分将PDF文件移动到另一个文件夹。以下是一些示例文件名:

Additional documents_Joe Root.pdf
Relevant past items_Joe Root.pdf
Important file list_Joe Root.pdf
Highly classified docs_David Williams.pdf
searched useful items_David Williams.pdf    

我需要将_Joe Root的文件移动到名为"Joe Root"的文件夹中 并将"_David Williams"的文件移至文件夹"David Williams"

2 个答案:

答案 0 :(得分:2)

答案

不要在这些文件的唯一副本上对此进行测试。制作您正在使用的根文件夹的副本。然后在副本上测试此代码。

# Create the folders. 
Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; $Matches[1] } | Select-Object -Unique | % { New-Item -ItemType directory $_ } | Out-Null

# Move the files. 
Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; Move-Item -Path $_ -Destination ".\$($Matches[1])\$_" } | Out-Null

演示

如果您想了解其工作原理,请按以下步骤操作。

  1. 创建一个空文件夹。
  2. Open Powershell。
  3. 将目录更改为空文件夹。
  4. 运行以下代码创建虚拟文件:

    $examples = @(
        "Additional documents_Joe Root.pdf",
        "Relevant past items_Joe Root.pdf",
        "Important file list_Joe Root.pdf",
        "Highly classified docs_David Williams.pdf",
        "searched useful items_David Williams.pdf"
    )
    
    $examples | % { New-Item -ItemType file $_ } | Out-Null
    
  5. 在此演示文件夹中运行第一个脚本。

  6. 请注意,没有子文件夹可以开头。工作尚未开始(没有文件被移动到子文件夹中。)所有PDF文件都在一个文件夹中。脚本完成了创建子文件夹和移动文件的工作。

    解释

    首先,让我更正原始代码。我使用了一个子表达式运算符$(),不需要它。

    $($_.BaseName)
    

    变为

    $_.BaseName
    

    以下是更新的命令:

    # Create the folders. 
    Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; $Matches[1] } | Select-Object -Unique | % { New-Item -ItemType directory $_ } | Out-Null
    
    # Move the files. 
    Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; Move-Item -Path $_ -Destination ".\$($Matches[1])\$_" } | Out-Null
    

    Get-ChildItem列出启动脚本的文件夹中的文件和文件夹。

    -Filter是Get-ChildItem的参数。它允许我限制返回的文件和文件夹。

    我更喜欢

    Get-ChildItem -Filter *.pdf
    

    因为它只会抓取PDF文件。

    Get-ChildItem *.pdf
    

    抓取文件夹中的所有文件,然后只显示PDF文件。

    不同之处在于流程中涉及的文件数量。使用-Filter更有效,因为提供程序在检索对象时应用它而不是检索所有对象然后过滤它们。当文件夹中有许多文件或引用远程计算机时,性能差异可能很大。

    由于我正在处理文件和文件夹,-Filter将调用FileSystem provider。 FileSystem提供程序识别globbing通配符。 *是一个通配符,表示匹配任意数量的任何字符。这对应于文件扩展名之前的文件名部分。 .pdf对应于文件扩展名。对于这个脚本,我只对PDF文件感兴趣。

    a test file.pdf
              *.pdf          
    

    | (pipe)cmdlets粘在一起。 PowerShell受益于两个主要功能。一,PowerShell是面向对象的。二,PowerShell有一个对象管道。这意味着您可以通过将一个cmdlet的输出连接到另一个cmdlet的输入来执行有意义的操作。

        cmdlet      cmdlet 
          v         v     
    Get-ChildItem | % {  }
                  ^       
                  pipe    
    

    因此,在命令的第一部分中,Get-ChildItem正在生成文件对象(System.IO.FileInfo)。而那些正被管道传输到管道中的其他cmdlet:ex。 Get-ChildItem的输出是%的输入。

    管道是通过管道连接的所有cmdlet。

    继续前进,%aliasForEach-Object。 ForEach-Object允许您将任意代码插入管道。这样,您可以自定义管道所做的工作,而不必仅依赖于cmdlet。

    关于ForEach-Object的第一个问题是如何指定管道中的当前对象。这是通过$ 完成的。 $ is an automatic variable,对应于管道中的当前对象。自动变量是PowerShell在某些上下文中自动提供的变量(如管道内部)。每次看到$ _时,我都会引用管道中的当前对象。

    Get-ChildItem将为它在文件系统上遇到的文件输出System.IO.FileInfo对象。 FileInfo对象有一个名为BaseName的ScriptProperty。此属性存储文件扩展名之前的文件名。

    ScriptProperties是为PowerShell对象计算的属性。您可以通过piping objects to Get-Member查看可用的ScriptProperties。您可能还会发现this page有帮助。

    所以,如果

    $a = Get-Item 'a test file.pdf'
    

    然后

    $a.BaseName
    

    生成一个测试文件' (没有引号)。这里,$ a是我的FileInfo对象。

    我命令中的点

    $a.BaseName
      ^
    

    property dereference operator。它让PowerShell知道我正在寻找存储在$ a中的FileInfo对象的BaseName属性。

    所以,到目前为止,你应该能够理解这个命令。

    Get-ChildItem -Filter *.pdf | % { $_.BaseName }
    

    它读取当前文件夹中的PDF文件,并为每个文件提供文件扩展名之前的文件名。

    管道中的下一个是-match operator。它是一个比较运算符。它将每个文件的BaseName与我的正则表达式进行比较,我将在后面解释。这里的一个微妙之处是我的输入是标量值(单个字符串)。

    每次

    $_.BaseName -match "_([a-zA-Z ]+)$"
    

    被评估,它仅在BaseName的当前值上运行,该值是单个字符串。

    字符串表示PowerShell中的文本。

    -match根据其输入是标量还是集合来更改其行为。对于标量输入,-match填充$ Matches自动变量,这很重要。

    regular expression是搜索模式。当我想在字符串中看到字母,数字或符号时,我告诉计算机。我说这个角色可以匹配多少次。我也可以申请其他规则。电脑给了我比赛。

    正则表达式的本质是表示字母和数字的抽象方式,以便计算机可以理解您要查找的内容。

    我的正则表达式是

    "_([a-zA-Z ]+)$"
    
    • a-z表示匹配任何小写字母字符,如a,b,c;等。
    • A-Z表示匹配任何大写字母字符。
    • []表示构建一个字符类。换句话说,匹配方括号[a-zA-Z]内的任何一个字符。还要注意我在我的角色类中包含一个空格。
    • +表示匹配类中的一个或多个字符。所以我不会只是匹配D'我会匹配David Williams'。
    • 括号表示将此部分匹配保存为单独的变量。换句话说,我想要大卫威廉姆斯' (没有下划线)。
    • _是字面下划线。它确保括号内的任何匹配都将出现在下划线之后。
    • $表示正则表达式的字符串结尾。我正在寻找_和字符串结尾之间的匹配。所以_和$是我正则表达式的结尾。请记住,我们只在这里查看BaseName。因此,.pdf不包含在字符串中。
    • 正则表达式保存为字符串。所以它出现在双引号中。

    这是形成正则表达式的过程。

    Highly classified docs_David Williams
    Step 1                _              $  Mark the ends of the match. 
    Step 2                _(            )$  Save the name as a backreference. 
    Step 3                _([a-z]       )$  Begin a character class that will match one lowercase letter. 
    Step 4                _([a-zA-Z])$      Match uppercase letters. Remove extra spaces. 
    Step 5                _([a-zA-Z ])$     Match the space between the first and last name. 
    Step 6                _([a-zA-Z ]+)$    Add + to match one or more characters from the character class. 
    

    VBScript有一个old article,它将我介绍给正则表达式。你可能会发现它很有帮助。 Regular-Expressions.info也是一个很好的参考。

    我没有解释$Matches automatic variable的重要性。请记住,对于标量输入,-match填充$ Matches自动变量。因此,括号内的匹配将保存为反向引用(正则表达式变量)。我稍后可以参考它。在这种特殊情况下,$Matches[1]会引用此人的姓名,例如David Williams'。

    现在您应该能够理解这段代码:

    Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" }
    

    它说当前文件夹中的PDF文件和每个文件,将人名保存到$Matches[1]并输出$ true。

    现在代码输出$ true的原因是因为-match的工作原理。当输入是标量时,-match返回一个布尔值,并将$ Matches自动变量的值设置为参数的匹配组件。由于我们不需要布尔值,我们可以丢弃它。 Out-Null丢弃它收到的任何输出。

    $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null;
    

    这里,Out-Null不是主要管道的一部分。它属于第一个ForEach-Object的脚本块。

    | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; $Matches[1] } | 
    ^                           The main pipeline.                      ^
    
    | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; $Matches[1] } | 
        ^                       %'s script block.                     ^  
    
    | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; $Matches[1] } | 
                           A local pipeline.  ^                          
    
    | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; $Matches[1] } | 
    A semicolon which separates statements on the same  ^  line.         
    

    继续前进,ForEach-Object脚本块中的$Matches[1]表示将人的姓名输出为字符串。

    现在您应该能够理解这段代码:

    Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; $Matches[1] }
    

    它将与每个文件关联的名称输出为字符串。

    我打算创建文件夹。我将通过将输出传递给cmdlet来实现这一点,该cmdlet为它接收的每个名称创建一个文件夹。

    问题是如果一个人与多个文件相关联,那么该命令将多次输出该人的姓名。 cmdlet创建文件夹将尝试多次创建同一文件夹。我想为每个独特的人提供一个字符串。

    因此,我使用-Unique参数将输出传递给Select-Object。这将输出它收到的每个名称的一个副本。

    Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; $Matches[1] } | Select-Object -Unique
    

    下一步是创建文件夹。我使用New-Item并指定-ItemType目录。这里目录是指文件夹。

    Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; $Matches[1] } | Select-Object -Unique | % { New-Item -ItemType directory $_ }
    

    最后一步是使用Out-Null丢弃New-Item的输出。这给出了创建文件夹的完整命令。

    Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; $Matches[1] } | Select-Object -Unique | % { New-Item -ItemType directory $_ } | Out-Null
    

    到目前为止,移动文件类似:

    Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; }
    

    在ForEach-Object脚本块中,我像以前一样匹配名称,但现在我使用匹配移动文件。

    移动文件

    -Path $_
    

    到文件夹

    -Destination ".\$($Matches[1])\$_"
    
    • 。\是本地文件夹:脚本工作的位置。
    • 这里我实际上需要子表达式运算符$()来将$Matches[1]的文本带入字符串。
    • 和\分隔文件夹就像在任何路径中一样。

    子表达式运算符$()计算任何PowerShell表达式并将结果插入字符串中。

    所以,

    > "$($Matches[1])"  # Produces
    David Williams
    

    > "$Matches[1]"
    System.Collections.Hashtable[1]
    

    如果没有子表达式运算符,该命令将提供System.Collections.Hashtable[1]而不是文件夹名称。

    以下是移动文件的完整命令:

    Get-ChildItem -Filter *.pdf | % { $_.BaseName -match "_([a-zA-Z ]+)$" | Out-Null; Move-Item -Path $_ -Destination ".\$($Matches[1])\$_" } | Out-Null
    

    和以前一样,我使用Out-Null丢弃移动的输出。

答案 1 :(得分:0)

试试这个:

$tartpath="c:\temp"

Get-ChildItem $tartpath -file -filter "*.pdf" | where name -like '*_*' |  %{
    $newdir="{0}\{1}" -f $tartpath, ($_.basename -split '_')[-1]
    new-Item $newdir -ItemType Directory -ErrorAction SilentlyContinue
    Move-Item $_.FullName $newdir
}