使用COPY命令合并文件时出现奇怪字符

5
我有一堆小的PowerShell函数,每个都在自己的文件中,并且我正在尝试将它们合并到一个更大的文件中。使用Windows复制命令的低技术方法,只需执行:copy /Y /A functions\*.ps1 super.ps1就可以了,但是当两个文件合并时,它会插入:。我猜测这是换行符出现的原因(编码不同),但是如何防止这些字符出现呢?
5个回答

4

你是否尝试使用 /B 参数来停止 OEM 字符集的转换?

谁知道呢。

或者你可以在记事本中打开脚本并将其保存为 ANSI 格式,然后尝试使用普通的复制命令来连接它们,无需使用 /B 参数。


是的 - 恰好相同的问题 - Robert MacLean
将文件保存为ASCII格式解决了一个几乎相同的问题。 - WillfulWizard
太好了。有趣的是,DOS类型的东西多年后仍在使用。 - kingchris

3

这些字符是Unicode字节顺序标记,它们将在.ps1内容之前无形地出现。

我不确定您如何在普通DOS中去除它们 - 在这一点上,我会转向脚本语言来完成这项工作。您可以编写一个PowerShell脚本,使用.NET System.IO功能将它们连接起来。


最终采用了使用PowerShell脚本将它们组合的解决方案,但是没有使用System.IO函数,而是使用了以下命令:Get-Content .\Functions*.ps1 | Out-File super.ps1 - Robert MacLean

1

这是一个有趣的方向

http://msdn.microsoft.com/en-us/library/aa368046(VS.85).aspx(无法链接此家伙)

这里有两种解决方案可以将您的 PowerShell 脚本从 Unicode 复制到 ANSI。一种解决方案使用 VB 编写,另一种使用 PowerShell 编写。

一旦转换为 Ansi,请遵循每个人的建议。


0
那些字符是字节序标记(BOM),正如Steve Gilham所说。 要删除BOM的最简单方法是在支持utf-8的文本编辑器中打开所有文件,进行小的编辑,然后再保存一次。 Gearny 是一个理想的选择。它支持utf-8,不保存BOM,而且是免费的。 它只适用于Windows,但我假设您可以访问Windows系统。 我不知道任何能够去除BOM的DOS程序(在DOS时代,utf-8只是一个项目)。
一旦去掉了BOM,您可以使用复制方法,但要在二进制模式(/b)下操作,否则每当出现多字节字符时,最可能的结果将是乱码文本。

是的 - 普通的Windows记事本也可以做到这一点,这是一个可以接受的解决方法,但我希望有一个不需要解决方法的解决方案。 - Robert MacLean
这个怎么样? http://www.howtogeek.com/howto/keyboard-ninja/keyboard-ninja-concatenate-multiple-text-files-in-windows/ - The Disintegrator

0

我强烈建议您不要使用DOS批处理程序来读写二进制文件,因为它并不适合这样做,批处理非常差劲。

这里有一个脚本,使用“fc”和“certutil”的黑客技巧来实现。但是它只适用于小文件。

如果您使用此脚本,请小心,您将需要使用十六进制编辑器编辑一些字符。

cls
@echo off
cd /d "%~dp0"
chcp 65001 >nul
set title_script=Concaténer des fichiers en ignorant le BOM
title %title_script%



rem Création des fichiers de test du script.

set bom_utf8=""
rem Cette ligne est spéciale. Entre les guillemets se trouvent
rem les 3 caractères hexadécimaux EF BB BF du BOM UTF-8
rem que l'on a écrit avec un éditeur hexadécimal.

if exist "_File 1.txt" del /f /q "_File 1.txt"
if exist "_File 2.txt" del /f /q "_File 2.txt"
if exist "_File 3.txt" del /f /q "_File 3.txt"
if exist "_FUSION.txt" del /f /q "_FUSION.txt"
echo|set /p=%bom_utf8%> "_File 1.txt"
echo|set /p=%bom_utf8%> "_File 2.txt"
echo|set /p=%bom_utf8%> "_File 3.txt"
echo "Fichier 1" >> "_File 1.txt"
echo "Fichier 2" >> "_File 2.txt"
echo "Fichier 3" >> "_File 3.txt"



rem ___________________________________________________________________________

rem Liste des fichiers à concaténer.
set FILE_1=%~dp0_File 1.txt
set FILE_2=%~dp0_File 2.txt
set FILE_3=%~dp0_File 3.txt

rem Nom du fichier concaténé.
set OUTPUT_FILE=%~dp0_FUSION.txt

rem ___________________________________________________________________________

rem Liste des Byte Order Mark (BOM).
set "BOM_UTF8=EFBBBF"
set "BOM_UTF16_LE=FFFE"
set "BOM_UTF16_BE=FEFF"
set "BOM_UTF32_LE=FFFE0000"
set "BOM_UTF32_BE=0000FEFF"

rem Taille maximum d'un BOM, ici 4 octets avec l'UTF-32.
set /a BOM_MAX_SIZE=4

rem Fichiers temporaires pour les commandes "fc" et "certutil".
set "TEMP_FILE_1=%~dpn0_tmp_1.txt"
set "TEMP_FILE_2=%~dpn0_tmp_2.txt"
set "TEMP_FILE_OUTPUT_1=%~dpn0_tmp_3.txt"
set "TEMP_FILE_OUTPUT_2=%~dpn0_tmp_4.txt"

set error_message_1=Erreur, le fichier : "%FILE_1%" n'existe pas. ^& echo.Ce script va s'arrêter.
rem error_message_1=Error, the file: "%FILE_1%" doesn't exist. ^& echo.This script will stop.
set /a switch=0
:define_variable
set error_message_2=Erreur : Le Byte Order Mark de "%CURRENT_FILE%" est "%FILE_BOM%" au lieu de "%FIRST_FILE_BOM%". ^& echo.Ce script va s'arrêter.
rem set error_message_2=Error: The Byte Order Mark in "%CURRENT_FILE%" is "%FILE_BOM%" instead of "%FIRST_FILE_BOM%". ^& echo.This script will stop.
if %switch% EQU 1 (goto variable_defined)
set "end_message=Operation accomplie."
rem set "end_message=Operation completed."



echo en-US - If the files to concatenate have a Byte Order Mark, this script is limited to small files. From 10000 bytes, this script will create more than 1254 temporary files, the operation may take a long time. Do you want to continue ? Yes/No (Y/N)
echo.
echo fr-FR - Si les fichiers à concatener ont un Byte Order Mark, ce script se limite aux petits fichiers. À partir de 10000 octets, ce script va créer plus de 1254 fichiers temporaires, l'opération risque de durer longtemps. Voulez-vous continuer ? Oui/Non (Y/N)
echo.
echo.
set RESPONSE=
set /P RESPONSE=Type your answer: %=%
if "%RESPONSE%"=="y" (goto yes)
if "%RESPONSE%"=="n" (goto no)
if "%RESPONSE%"=="Y" (goto yes)
if "%RESPONSE%"=="N" (goto no)
:no
exit /b 0
:yes
echo.



rem Si le premier fichier n'existe pas, on stoppe le programme en renvoyant le code d'erreur -1.
if not exist "%FILE_1%" (
    echo %error_message_1%
    echo.
    pause
    exit /b -1
)



rem On détecte le BOM du premier fichier.
set "FILE_BOM=NONE"
call :my_function_read_bom FILE_1 FILE_BOM
set "FIRST_FILE_BOM=%FILE_BOM%"
echo %FILE_1% : %FIRST_FILE_BOM%

rem On crée le fichier de sortie.
copy /v /y /b "%FILE_1%" "%OUTPUT_FILE%" /b
echo.

rem S'il n'y a pas de BOM sur le premier fichier, c'est du binaire, de l'ASCII ou de l'ANSI, donc on copie tout.
rem On prend chaque fichier suivant avec une boucle while en utilisant un nom de variable dynamique (avec la commande "goto").
set /a count_loop=2
setlocal enabledelayedexpansion
set "CURRENT_FILE=!FILE_%count_loop%!"
rem Cette concaténation de variables ne fonctionne pas dans un bloc if.
:while_loop_1
    if "%FIRST_FILE_BOM%"=="NONE" (
        copy /v /y /b "%OUTPUT_FILE%" + /b "!CURRENT_FILE!" "%OUTPUT_FILE%" /b
    ) else (goto end_loop_1)
    set /a count_loop=count_loop+1
    set "CURRENT_FILE=!FILE_%count_loop%!"
    if exist "!CURRENT_FILE!" (goto while_loop_1)
    echo.
    echo %end_message%
    echo.
    pause
    exit /b 0
:end_loop_1
endlocal

rem S'il y a un BOM, il faut copier le contenu du fichier en le supprimant.
set /a count_loop=2
setlocal enabledelayedexpansion
set "CURRENT_FILE=!FILE_%count_loop%!"
:while_loop_2
    rem On vérifie le BOM.
    set "FILE_BOM=NONE"
    call :my_function_read_bom CURRENT_FILE FILE_BOM
    echo !CURRENT_FILE! : !FILE_BOM!

    rem Si le BOM est différent du fichier de départ, on stoppe avec le code d'erreur -2.
    if not "!FILE_BOM!"=="!FIRST_FILE_BOM!" (
        echo.
        set /a switch=1
        goto define_variable
        :variable_defined
        echo %error_message_2%
        echo.
        pause
        exit /b -2
    )

    rem On crée une copie temporaire du fichier à ajouter mais sans son BOM.
    if "!FILE_BOM!"=="UTF8" (set /a SIZE_OF_BOM=3)
    if "!FILE_BOM!"=="UTF16LE" (set /a SIZE_OF_BOM=2)
    if "!FILE_BOM!"=="UTF16BE" (set /a SIZE_OF_BOM=2)
    if "!FILE_BOM!"=="UTF32LE" (set /a SIZE_OF_BOM=4)
    if "!FILE_BOM!"=="UTF32BE" (set /a SIZE_OF_BOM=4)
    if "!FILE_BOM!"=="NONE" (exit /b -3)
    call :my_function_split_file CURRENT_FILE SIZE_OF_BOM

    rem On ajoute cette copie temporaire.
    copy /V /Y /B "%OUTPUT_FILE%" + /B "!CURRENT_FILE!_part.2" "%OUTPUT_FILE%" /B
    echo.

    rem Puis on supprime cette copie temporaire.
    del /F /Q "!CURRENT_FILE!_part.2" >nul 2>&1

    rem Tous les programmes (donc toutes les commandes) ont 3 flux :
    rem Standard Input  = Le flux des entrées (clavier pour la console)
    rem Standard Output = Le flux des sorties (interface utilisateur et écran)
    rem Standard Error  = Le flux des erreurs (messages d'erreur affichés)
    rem L'indicatif 1>nul ou par défaut >nul annule le flux des sorties, la commande n'affiche rien sauf en cas d'erreur.
    rem L'indicatif 2>nul annule le flux des erreurs, la commande affiche tout sauf les erreurs.
    rem L'indicatif 2>&1  permet de réorienter le flux des erreurs sur le flux des sorties.
    rem Donc >nul 2>&1 permet de ne jamais rien afficher, même en cas d'erreur.

    set /a count_loop=count_loop+1
    set CURRENT_FILE=!FILE_%count_loop%!
    if exist "%CURRENT_FILE%" (goto while_loop_2)
endlocal



echo.
echo %end_message%
echo.
pause

rem Destruction des fichiers de test du script.
if exist "%FILE_1%" del /f /q "%FILE_1%"
if exist "%FILE_2%" del /f /q "%FILE_2%"
if exist "%FILE_3%" del /f /q "%FILE_3%"
if exist "%OUTPUT_FILE%" del /f /q "%OUTPUT_FILE%"

exit /b 0
rem Il ne faut pas oublier le "exit" sinon les fonctions qui suivent seront à nouveau lues comme du code simple et cela causera des problèmes.

rem ___________________________________________________________________________

:my_function_read_bom
    setlocal enabledelayedexpansion
    set "FILE_INPUT=!%~1!"

    rem On supprime le fichier temporaire s'il existe déjà.
    del /f /q "%TEMP_FILE_1%" >nul 2>&1
    del /f /q "%TEMP_FILE_OUTPUT_1%" >nul 2>&1

    rem On crée le fichier temporaire et on le remplit de zéros selon la taille à comparer (soit 4 octets à 00).
    rem Goto eof sert à arrêter si le fichier temporaire n'a pas pu être créé.
    fsutil file createnew "%TEMP_FILE_1%" !BOM_MAX_SIZE! >nul || goto :eof

    set /a COUNT_LINE=1
    >"%TEMP_FILE_OUTPUT_1%" (
        rem Pour lire le fichier en binaire, on utilise un hack de la commande "fc".
        rem La commande "fc" permet d'afficher une comparaison binaire octet par octet entre deux fichiers.
        rem Elle n'affiche que les octets qui ne correspondent pas, donc elle n'affichera pas les zéros.
        rem On utilise la boucle "for /f" pour extraire les octets du résultats de la commande "fc".
        rem Avec "skip", on saute la première ligne.
        rem Avec "delims", on sépare chaque ligne selon les caractères deux-points et espace.
        rem Avec "tokens", on place le contenu séparé dans 2 variables (i et j).
        rem Donc la variable i contiendra l'offset et la variable j contiendra l'octet.
        for /f "skip=1 tokens=1,2 delims=: " %%i in ('fc /b "%FILE_INPUT%" "%TEMP_FILE_1%"') do (
            rem À la fin, la commande FC affiche un comparatif de taille qui commence par des caractères
            rem dont les octets sont "46 43 EFBFBD", ils sont inscrits ici avec un éditeur hexadécimal.
            if not "%%i"=="FC�" (
                set /a OFFSET=0x%%i

                rem Si l'octet est 00, la commande "fc" ne les affiche pas.
                for /l %%k in (!COUNT_LINE!,1,!OFFSET!) do (<nul set /p "=00")

                rem On écrit l'octet dans le fichier.
                <nul set /p "=%%j"

                set /a COUNT_LINE=OFFSET+2
            )
        )
        rem Si les derniers octets sont 00, la commande "fc" ne les affiche pas.
        for /l %%i in (!COUNT_LINE!,1,!BOM_MAX_SIZE!) do (<nul set /p "=00")
    )
    del /f /q "%TEMP_FILE_1%" >nul 2>&1

    rem Le fichier de sortie contient les premiers octets du fichier à lire.
    rem On lit les premiers caractères de ce fichier de sortie.
    rem On utilise la boucle "for /f" pour lire le fichier ligne par ligne.
    rem Avec "usebackq", on peut lire les noms de fichiers ayant des espaces.
    for /f "usebackq tokens=* delims=" %%i in ("%TEMP_FILE_OUTPUT_1%") do (
        set "LINE_READ=%%i"

        rem On en déduit le BOM du fichier.
        if "!FILE_BOM!"=="NONE" (
            set BOM_READ=!LINE_READ:~0,8!
            if !BOM_READ!==%BOM_UTF32_LE% (set "FILE_BOM=UTF32LE")
        )
        if "!FILE_BOM!"=="NONE" (
            set BOM_READ=!LINE_READ:~0,8!
            if !BOM_READ!==%BOM_UTF32_BE% (set "FILE_BOM=UTF32BE")
        )
        if "!FILE_BOM!"=="NONE" (
            set BOM_READ=!LINE_READ:~0,6!
            if !BOM_READ!==%BOM_UTF8% (set "FILE_BOM=UTF8")
        )
        if "!FILE_BOM!"=="NONE" (
            set BOM_READ=!LINE_READ:~0,4!
            if !BOM_READ!==%BOM_UTF16_LE% (set "FILE_BOM=UTF16LE")
        )
        if "!FILE_BOM!"=="NONE" (
            set BOM_READ=!LINE_READ:~0,4!
            if !BOM_READ!==%BOM_UTF16_BE% (set "FILE_BOM=UTF16BE")
        )
    )
    del /f /q "%TEMP_FILE_OUTPUT_1%" >nul 2>&1
    endlocal & set %2=%FILE_BOM%
goto :eof

:my_function_split_file
    rem On importe le nom du fichier et on retire les guillemets.
    set "INPUT_FILE=!%~1!"
    set /a BUFFER=7800

    rem On supprime les fichiers temporaires.
    rem Le préfixe "/a:h" est indispensable pour pouvoir aussi supprimer les fichiers cachés.
    del /f /q "%TEMP_FILE_2%" >nul 2>&1
    del /f /q /a:h "%TEMP_FILE_OUTPUT_2%_part.*" >nul 2>&1

    rem Pour écrire dans un fichier en binaire, on utilise un hack de la commande "certutil".
    rem Cette commande crée un fichier texte plus gros que le fichier d'origine.
    rem La commande crée un fichier texte contenant une vue du fichier à la façon d'un éditeur hexadécimal.

    rem Elle découpe le fichier en lignes de 16 octets. Chaque ligne se compose de...
    rem Trois caractères qui indiquent un numéro de ligne : 000, 001, 002... 009, 00a, 00b... 00f, 010, 011, 012... fff.
    rem La commande est donc limitée à 4096 lignes (de 0 à fff). Donc la taille du fichier est limitée à 65536 octets.
    rem Puis il y a un 0 (30) et une tabulation (09).
    rem Puis le code hexadécimal est écrit au format ASCII en deux groupes de 8 octets séparés par un espace.
    rem Puis il y a 3 espaces (ou plus, pour que la partie suivante arrive à la colonne 57 du fichier texte).
    rem Puis le code binaire du fichier est recopié en remplaçant chaque caractère non-textuel par un point (2E).
    rem Exemple d'un fichier en UTF-8 contenant la ligne "abcdefghijklm".
    rem Cela donne "0000    EF BB BF 61 62 63 64 65  66 67 68 69 6a 6b 6c 6d   ...abcdefghijklm".
    rem Enfin la ligne se termine par un retour à la ligne (0D0A).

    rem Le paramètre "f" sert à écraser le fichier existant (force overwrite).
    certutil -encodehex -f "%INPUT_FILE%" "%TEMP_FILE_2%" >nul

    rem SIZE est le nombre d'octets du BOM qu'il faudra retirer.
    set /a "SIZE=!%~2!"

    if not defined SIZE (echo SIZE undefined. & goto :eof)
    if !SIZE! LEQ 0 (echo SIZE not valid. & goto :eof)

    rem LENGTH la longueur du BOM en nombre de caractères.
    rem Par exemple en UTF-8, EFBBBF prend 3 octets mais 6 caractères.
    rem Il faut multiplier par 2 car un octet s'écrit avec 2 caractères.
    set /a LENGTH=%SIZE%*2

    set "HEXADECIMAL_STRING="
    set /a HEXADECIMAL_STRING_LENGTH=0
    set /a PART=1

    rem On lit le fichier texte créé, et on le découpe en mettant chaque ligne en un fichier différent.
    rem Le délimitateur est le caractère "Tabulation" (09).
    for /f "usebackq tokens=2 delims=   " %%a in ("%TEMP_FILE_2%") do (
        set "CURRENT_LINE=%%a"

        rem Chaque ligne fait 72 caractères de long. 4 pour le numéro de ligne,
        rem 1 pour la tabulation, 48 pour la partie hexadécimale et 19 pour le texte.
        rem On extrait les 48 caractères pour n'obtenir que la partie hexadécimale.
        rem Avec notre exemple, on obtient donc : "ef bb bf 61 62 63 64 65  66 67 68 69 6a 6b 6c 6d".
        set HEXADECIMAL_STRING=!HEXADECIMAL_STRING!!CURRENT_LINE:~0,48!

        rem On supprime les espaces, ce qui fait 32 caractères.
        rem Avec notre exemple, on obtient donc : "efbbbf6162636465666768696a6b6c6d".
        set HEXADECIMAL_STRING=!HEXADECIMAL_STRING: =!

        if !HEXADECIMAL_STRING_LENGTH! GEQ 32 (
            set /a HEXADECIMAL_STRING_LENGTH=HEXADECIMAL_STRING_LENGTH+32
        )
        if !HEXADECIMAL_STRING_LENGTH! LSS 32 (
            call :function_strlen HEXADECIMAL_STRING HEXADECIMAL_STRING_LENGTH
        )
        rem Nombre de caractères que le fichier contient / Nombre maximum que l'on peut en extraire.
        echo %INPUT_FILE% : !HEXADECIMAL_STRING_LENGTH! / %BUFFER%

        rem Il faut extraire le nombre de caractères LENGTH.
        rem La ligne contient HEXADECIMAL_STRING_LENGTH caractères.
        rem Si la taille de la ligne est >= la taille du BOM à extraire,
        rem alors le BOM à extraire est entièrement contenu dans la ligne,
        rem donc on sauvegarde la partie à extraire dans un fichier.
        if !HEXADECIMAL_STRING_LENGTH! GEQ !LENGTH! (
            set /a REST=HEXADECIMAL_STRING_LENGTH-LENGTH
            for %%i in (!REST!) do (
                echo(!HEXADECIMAL_STRING:~0,-%%i!>>"%TEMP_FILE_OUTPUT_2%_part.!PART!"
                rem Avec notre exemple, le fichier contient donc les caractères
                rem "efbbbf" suivi d'un retour à la ligne (0D0A).

                rem On cache le fichier.
                attrib +h "%TEMP_FILE_OUTPUT_2%_part.!PART!" >nul

                set HEXADECIMAL_STRING=!HEXADECIMAL_STRING:~-%%i!
                rem La variable contient désormais le reste de la ligne,
                rem dans notre exemple : "6162636465666768696a6b6c6d".

                set /a HEXADECIMAL_STRING_LENGTH=REST
            )
            rem On utilise "certutil" pour créer un fichier binaire à partir du fichier texte.
            certutil -decodehex -f "%TEMP_FILE_OUTPUT_2%_part.!PART!" "%INPUT_FILE%_part.!PART!" >nul

            set /a PART=PART+1
            set /a LENGTH=%SIZE%*2
        )

        rem Si la taille de la ligne est >= la taille maximum de sécurité,
        rem on sauvegarde la ligne dans un fichier et on réinitialise.
        if !HEXADECIMAL_STRING_LENGTH! GEQ !BUFFER! (
            echo(!HEXADECIMAL_STRING!>>"%TEMP_FILE_OUTPUT_2%_part.!PART!"
            attrib +h "%TEMP_FILE_OUTPUT_2%_part.!PART!" >nul
            set "HEXADECIMAL_STRING="
            set /a HEXADECIMAL_STRING_LENGTH=0

            set /a LENGTH=!LENGTH!-!BUFFER!
            if !LENGTH! LSS 0 (set /a LENGTH=0)
        )
    )
    rem Au final, il reste la variable contenant le reste de la ligne, on l'extrait dans un autre fichier.
    echo !HEXADECIMAL_STRING!>>"%TEMP_FILE_OUTPUT_2%_part.!PART!"
    attrib +h "%TEMP_FILE_OUTPUT_2%_part.!PART!" >nul
    certutil -decodehex -f "%TEMP_FILE_OUTPUT_2%_part.!PART!" "%INPUT_FILE%_part.!PART!" >nul

    del /f /q "%TEMP_FILE_2%" >nul 2>&1
    del /f /q /a:h "%TEMP_FILE_OUTPUT_2%_part.*" >nul 2>&1

    rem On fusionne toutes les autres parties dans la deuxième.
    if !PART! GEQ 3 (
        for /l %%i in (3,1,!PART!) do (
            copy /v /y /b "%INPUT_FILE%_part.2" + /b "%INPUT_FILE%_part.%%i" "%INPUT_FILE%_part.2" /b >nul
            del /f /q "%INPUT_FILE%_part.%%i" >nul 2>&1
        )
    )

    rem On supprime la première partie.
    del /f /q "%INPUT_FILE%_part.1" >nul 2>&1
goto :eof

:function_strlen
    setlocal enabledelayedexpansion
    set "str=!%~1!#"
    set "length=0"
    for %%i in (8184 4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
        if "!str:~%%i,1!" NEQ "" ( 
            set /a "length+=%%i"
            set "str=!str:~%%i!"
        )
    )
    endlocal & set "%~2=%length%"
goto :eof

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接