Замена всех GUID в файле новыми GUID из командной строки

У меня есть файл, содержащий большое количество вхождений строки Guid="GUID HERE" (где GUID HERE — уникальный GUID для каждого вхождения), и я хочу заменить каждый существующий GUID новым уникальным GUID.

Это на машине разработки Windows, поэтому я могу генерировать уникальные идентификаторы GUID с помощью uuidgen.exe (который создает GUID на стандартном выводе каждый раз при запуске). У меня есть sed и такие в наличии (но awk как ни странно нет).

Я в основном пытаюсь выяснить, возможно ли (и если да, то как) использовать вывод программы командной строки в качестве заменяющего текста в выражении подстановки sed, чтобы я мог сделать эту замену с минимальными усилиями на моя часть. Мне не нужно использовать sed -- если есть другой способ сделать это, например, какой-нибудь сумасшедший vim-fu или какая-нибудь другая программа, которая тоже будет работать -- но я бы предпочел решения, использующие минимальный набор * nix-программы, так как на самом деле я не работаю с *nix-машинами.

Чтобы было ясно, если у меня есть такой файл:

etc etc Guid="A" etc etc Guid="B"

Я хотел бы, чтобы это стало так:

etc etc Guid="C" etc etc Guid="D"

где A, B, C, D — это, конечно, фактические GUID.

(например, я видел, как xargs использовался для вещей, подобных этому, но он также недоступен на машинах, на которых мне это нужно. Я мог бы установить его, если это действительно единственный способ, хотя я бы не хотел)


person Community    schedule 04.02.2010    source источник
comment
Вы знакомы с PowerShell?   -  person    schedule 04.02.2010
comment
Я знаю об этом, но я не использую его регулярно и не устанавливаю его. Если есть жизнеспособное решение, я только за.   -  person    schedule 04.02.2010


Ответы (5)


Я переписал решение C# в PowerShell. Я подумал, что вам будет проще запустить скрипт powershell, чем скомпилировать исполняемый файл C #.

Шаги для использования этого:

  1. Скачать/установить powershell
  2. Сохраните приведенный ниже код где-нибудь с именем GuidSwap.ps1.
  3. Измените переменные $filename и $outputFilename в соответствии с вашими потребностями.
  4. Запустите powershell -noexit c:\location\to\guidswap.ps1.

## GuidSwap.ps1
##
## Reads a file, finds any GUIDs in the file, and swaps them for a NewGUID
##

$filename = "d:\test.txt"
$outputFilename = "d:\test_new.txt"

$text = [string]::join([environment]::newline, (get-content -path $filename))

$sbNew = new-object system.text.stringBuilder

$pattern = "[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}"

$lastStart = 0
$null = ([regex]::matches($text, $pattern) | %{
    $sbNew.Append($text.Substring($lastStart, $_.Index - $lastStart))
    $guid = [system.guid]::newguid()
    $sbNew.Append($guid)
    $lastStart = $_.Index + $_.Length
})
$null = $sbNew.Append($text.Substring($lastStart))

$sbNew.ToString() | out-file -encoding ASCII $outputFilename

Write-Output "Done"
person BigJoe714    schedule 17.02.2010
comment
Я разработчик, я не уверен, где вы видели иначе. Однако это будет полезнее, чем писать для этого приложение на C#. - person ; 17.02.2010
comment
Извини за это. Это был другой вопрос, где я это видел. - person BigJoe714; 17.02.2010
comment
Я хотел бы проголосовать за это больше. Это сэкономило мне так много времени. - person Dan Field; 11.03.2015

Готовы ли вы скомпилировать консольное приложение C# для этого? Я очень быстро все это затеял. Он принимает имя файла в качестве аргумента командной строки, находит все, что похоже на GUID, заменяет его новым GUID и записывает новое содержимое файла.

Посмотри:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace GUIDSwap
{
    class Program
    {
        static int Main(string[] args)
        {
            try
            {
                if (args.Length == 0) throw new ApplicationException("No filename specified");

                string filename = args[0];
                filename = filename.TrimStart(new char[] { '"' }).TrimEnd(new char[] { '"' });

                if (!File.Exists(filename)) throw new ApplicationException("File not found");

                StreamReader sr = new StreamReader(filename);
                string text = sr.ReadToEnd();
                sr.Close();

                StringBuilder sbNew = new StringBuilder();

                string pattern = "[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}";

                int lastStart = 0;
                foreach (Match m in Regex.Matches(text, pattern))
                {
                    sbNew.Append(text.Substring(lastStart, m.Index - lastStart));
                    sbNew.Append(Guid.NewGuid().ToString());
                    lastStart = m.Index + m.Length;
                }

                sbNew.Append(text.Substring(lastStart));

                StreamWriter sw = new StreamWriter(filename, false);
                sw.Write(sbNew.ToString());
                sw.Flush();
                sw.Close();

                return 0;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return 1;
            }
        }
    }
}
person BigJoe714    schedule 16.02.2010

Я искал способ заменить все идентификаторы GUID в решении Visual Studio, поэтому я взял ответ на этот вопрос StackOverflow (GuidSwap.ps1) и расширил его, чтобы сценарий отслеживал идентификаторы GUID, на которые ссылаются несколько файлов. Пример показан в шапке ниже.

<#
    .Synopsis
    Replace all GUIDs in specified files under a root folder, carefully keeping track 
    of how GUIDs are referenced in different files (e.g. Visual Studio solution).

    Loosely based on GuidSwap.ps1:
    http://stackoverflow.com/questions/2201740/replacing-all-guids-in-a-file-with-new-guids-from-the-command-line

    .NOTES
    Version:        1.0
    Author:         Joe Zamora (blog.idmware.com)
    Creation Date:  2016-03-01
    Purpose/Change: Initial script development

    .EXAMPLE
    .\ReplaceGuids.ps1 "C:\Code\IDMware" -FileNamePatterns @("*.sln","*.csproj","*.cs") -Verbose -WhatIf
#>

# Add common parameters to the script.
[CmdletBinding()]
param(
    $RootFolder
    ,$LogFolder='.'
    ,[String[]]$FileNamePatterns
    ,[switch]$WhatIf
)
$global:WhatIf = $WhatIf.IsPresent

# Change directory to the location of this script.
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
cd $dir
$ScriptName = $MyInvocation.MyCommand.Name

If(!($RootFolder))
{
    Write-Host @"
Usage: $ScriptName  -RootFolder <RootFolder> [Options]

Options:
    -LogFolder <LogFolder>                      Defaults to location of script.

    -FileNamePatterns @(*.ext1, *.ext2, ...)    Defaults to all files (*).

    -WhatIf                                     Test run without replacements.

    -Verbose                                    Standard Powershell flags.
    -Debug
"@
    Exit
}

if ($LogFolder -and !(Test-Path "$LogFolder" -PathType Container))
{
    Write-Host "No such folder: '$LogFolder'"
    Exit
}

<#
    .Synopsis
    This code snippet gets all the files in $Path that contain the specified pattern.
    Based on this sample:
    http://www.adminarsenal.com/admin-arsenal-blog/powershell-searching-through-files-for-matching-strings/
#>
function Enumerate-FilesContainingPattern {
[CmdletBinding()]
param(
    $Path=(throw 'Path cannot be empty.')
    ,$Pattern=(throw 'Pattern cannot be empty.')
    ,[String[]]$FileNamePatterns=$null
)
    $PathArray = @()
    if (!$FileNamePatterns) {
        $FileNamePatterns = @("*")
    }

    ForEach ($FileNamePattern in $FileNamePatterns) {
        Get-ChildItem $Path -Recurse -Filter $FileNamePattern |
        Where-Object { $_.Attributes -ne "Directory"} |
        ForEach-Object {
            If (Get-Content $_.FullName | Select-String -Pattern $Pattern) {
                $PathArray += $_.FullName
            }
        }
    }
    $PathArray
} <# function Enumerate-FilesContainingPattern #>

# Timestamps and performance.
$stopWatch = [System.Diagnostics.Stopwatch]::StartNew()
$startTime = Get-Date
Write-Verbose @"

--- SCRIPT BEGIN $ScriptName $startTime ---

"@

# Begin by finding all files under the root folder that contain a GUID pattern.
$GuidRegexPattern = "[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}"
$FileList = Enumerate-FilesContainingPattern $RootFolder $GuidRegexPattern $FileNamePatterns

$LogFilePrefix = "{0}-{1}" -f $ScriptName, $startTime.ToString("yyyy-MM-dd_HH-mm-ss")
$FileListLogFile = Join-Path $LogFolder "$LogFilePrefix-FileList.txt"
$FileList | ForEach-Object {$_ | Out-File $FileListLogFile -Append}
Write-Host "File list log file:`r`n$FileListLogFile"
cat $FileListLogFile | %{Write-Verbose $_}

# Next, do a read-only loop over the files and build a mapping table of old to new GUIDs.
$guidMap = @{}
foreach ($filePath in $FileList)
{
    $text = [string]::join([environment]::newline, (get-content -path $filePath))
    Foreach ($match in [regex]::matches($text, $GuidRegexPattern)) {
        $oldGuid = $match.Value.ToUpper()
        if (!$guidMap.ContainsKey($oldGuid)) {
            $newGuid = [System.Guid]::newguid().ToString().ToUpper()
            $guidMap[$oldGuid] = $newGuid
        }
    }
}

$GuidMapLogFile = Join-Path $LogFolder "$LogFilePrefix-GuidMap.csv"
"OldGuid,NewGuid" | Out-File $GuidMapLogFile
$guidMap.Keys | % { "$_,$($guidMap[$_])" | Out-File $GuidMapLogFile -Append }
Write-Host "GUID map log file:`r`n$GuidMapLogFile"
cat $GuidMapLogFile | %{Write-Verbose $_}

# Finally, do the search-and-replace.
foreach ($filePath in $FileList) {
    Write-Verbose "Processing $filePath"
    $newText = New-Object System.Text.StringBuilder
    cat $filePath | % { 
        $original = $_
        $new = $_
        $isMatch = $false
        $matches = [regex]::Matches($new, $GuidRegexPattern)
        foreach ($match in $matches) {
            $isMatch = $true
            $new = $new -ireplace $match.Value, $guidMap[$match.Value.ToString().ToUpper()]
        }        
        $newText.AppendLine($new) | Out-Null
        if ($isMatch) {
            $msg = "Old: $original`r`nNew: $new"
            if ($global:WhatIf) {
                Write-Host "What if:`r`n$msg"
            } else {
                Write-Verbose "`r`n$msg"
            }
        }
    }
    if (!$global:WhatIf) {
        $newText.ToString() | Set-Content $filePath
    }
}

# Timestamps and performance.
$endTime = Get-Date
Write-Verbose @"

--- SCRIPT END $ScriptName $endTime ---

Total elapsed: $($stopWatch.Elapsed)
"@
person Joe Zamora    schedule 02.03.2016
comment
Возможно, вы захотите рассмотреть возможность создания расширения VS, которое вызывает этот ваш скрипт. Это отлично сработало для меня, и вы могли бы принимать добровольные пожертвования таким образом :-) - person Ed Bayiates; 17.09.2020
comment
У вас есть мое разрешение получать прибыль от вашего расширения VS, используя мой скрипт. Удачи! Ваше здоровье! Б^) - person Joe Zamora; 18.09.2020

вы можете просто сначала записать uid в переменную, а затем выполнить sed?

@echo off
setlocal enabledelayedexpansion
for /f %%x in ('uuidgen.exe') do (
        set uid=%%x
)
sed -e "s/Guid=\"\(.*\)\"/Guid=\"!uid!\"/g" file
person ghostdog74    schedule 05.02.2010
comment
Будьте осторожны, так как использование .* в ваших регулярных выражениях может привести к тому, что он захватит больше, чем вы ожидаете, в зависимости от режима, в котором он запущен. Вместо этого вы можете рассмотреть возможность выполнения чего-то вроде [^]+ (обратите внимание, что он явно никогда не проглотит литерал ) - person Steven Schlansker; 05.02.2010
comment
Похоже, что каждый GUID в файле заменяется одним и тем же новым GUID (когда я пытаюсь это сделать). Мне нужно заменить каждый GUID новым уникальным GUID. - person ; 05.02.2010

Мне очень нравится решение BigJoe714. Я сделал еще один шаг вперед, найдя все определенные файлы расширений и заменив все идентификаторы GUID.

<pre>
<code>
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace AllGuidSwap
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                if (args.Length == 0) throw new ApplicationException("No filename specified");

                string directory = args[0]; //Path
                string extensionToFind = args[1]; //Extension to find

                if (!Directory.Exists(directory)) throw new ApplicationException("directory not found");

                var allFiles = Directory.GetFiles(directory).Where(a => a.EndsWith(extensionToFind));

                foreach (var filename in allFiles)
                {
                    if (!File.Exists(filename)) throw new ApplicationException("File not found");

                    var sr = new StreamReader(filename);
                    var text = sr.ReadToEnd();
                    sr.Close();

                    var sbNew = new StringBuilder();

                    var pattern = "[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}";

                    var lastStart = 0;
                    foreach (Match m in Regex.Matches(text, pattern))
                    {
                        sbNew.Append(text.Substring(lastStart, m.Index - lastStart));
                        sbNew.Append(Guid.NewGuid().ToString().ToUpperInvariant());
                        lastStart = m.Index + m.Length;
                    }

                    sbNew.Append(text.Substring(lastStart));

                    var sw = new StreamWriter(filename, false);
                    sw.Write(sbNew.ToString());
                    sw.Flush();
                    sw.Close();
                }

                Console.WriteLine("Successful");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadKey();
        }
    }
}

</code>
</pre>
person Jayesh Modha    schedule 16.04.2014