【问题标题】:Use the contents of a file to rename it [duplicate]使用文件的内容重命名它[重复]
【发布时间】:2013-06-11 10:24:25
【问题描述】:

我有大约 20 MB 大小且具有随机文件名的二进制数据文件,均以“AA”开头。在每个文件的内容中,它们在固定位置都有一个特定的字符串(在所有文件中从第 2086 个字节开始)。我想读取由 2 个单词组成的字符串(在下面的示例中,中间有 1 个空格,如“MyName Sirname”)并将其与文件创建日期一起使用来重命名文件。

这是一个缩短的示例文件(前 3Kb): dl.dropboxusercontent.com/u/18286876/short.zhr

我们想将此特定文件重命名为“MyName Sirname YYYY-MM-DD”。

最好让脚本遍历 currant 目录中所有以“AA”开头的文件。脚本可以是 vbs 或 batch+vbs 组合,只要更简单。

这可能看起来是重复的,但最初的问题缺乏细节,错误地集中在批处理上并且给定的答案不充分。

【问题讨论】:

  • 我无法访问dl.dropboxusercontent.com/u/18286876/short.zhr的文件。
  • 你想要有人为你创建这个脚本吗?
  • 注意"MyName Sirname YYYY-MM-DD"中的“搞笑字符”实际上是表示“Sirname”长度的长度字节(ASCII 7)。我的回答显示了如何使用长度字节“正确处理”。 /cc @STLDevoper
  • @virusrocks - 非常欢迎!我是医学博士,我以前只知道一点点的语言是 php。我大胆尝试自己解决它,但这是浪费时间。是的,它是一个扩展副本(@Endoro),但在之前的帖子中,我只得到一个提示,它不能只用批处理文件来完成。所以我再次寻求帮助。
  • 您可以使用命令行十六进制转储程序读取此文件,例如。 xxd 并使用 sed 获取所需的日期信息。这也许可以。

标签: string file batch-file vbscript


【解决方案1】:

假设您的所有文件都在同一个文件夹中C:\some\where,这样的事情可能会起作用:

Const offset = 2085

Set fso = CreateObject("Scripting.FileSystemObject")

For Each f In fso.GetFolder("C:\some\where").Files
  If Left(f.Name, 2) = "AA" Then
    Set stream = f.OpenAsTextStream
    stream.Skip(offset)

    words = Array()
    Do
      length = Asc(stream.Read(1))
      If length <> 0 Then
        ReDim Preserve words(UBound(words)+1)
        words(UBound(words)) = stream.Read(length)
      End If
    Loop Until length = 0 Or stream.AtEndOfStream

    stream.Close

    If UBound(words) >= 1 Then
      fdate = Year(f.DateCreated) & "-" & Right("0" & Month(f.DateCreated), 2) _
        & "-" & Right("0" & Day(f.DateCreated), 2)
      f.Name = words(0) & " " & words(1) & " " & fdate _
        & "." & fso.GetExtensionName(f.Name)
    End If
  End If
Next

【讨论】:

  • 输出错误:第 21 行,字符 5,索引超出范围:'[number:1]',代码 800A0009
  • 处理后的文件在给定的偏移量处似乎只有一个单词(不像您的示例文件,它有 4 个)。
  • 我使用了发布的相同文件
  • 偏移量必须是 2085,而不是 2086。固定。
  • f.OpenAsTextStream - 我在那里停止阅读
【解决方案2】:

注意该文件似乎是“复合文档文件 V2 文档”格式。可能有一些图书馆可以以适当的方式阅读它。

大胆猜测:您是否试图“阅读” Outlook .msg 文件、word/excel 文档?

使用file 或查看


更新添加了 C++ 版本(见下文

对该文件稍作修改告诉我这是一个二进制文件,字符串没有分隔,但前面是长度字节。所以,这个 bash 脚本应该可以正常工作:

#!/bin/bash
set -e # stop on errors

for originalname in "$@"
do
    # get lengths
    first_len=$(od -j 2085 "$originalname" -An -t u1 -N1)
    second_len=$(od -j $((2086 + $first_len)) "$originalname" -An -t u1 -N1)

    # strip whitespace
    read first_len second_len <<< "$first_len $second_len"

    # extract the words as text
    firstword=$(dd if="$originalname" bs=1 skip=2086 count=$first_len)
    secondword=$(dd if="$originalname" bs=1 skip=$((2087+$first_len)) count=$second_len)

    # calculate new name, using the timestamp of the file too:
    newname="$firstword $secondword $(date -r "$originalname" +"%Y-%m-%d")"

    # do the move (verbosely)
    mv -v "$originalname" "$(dirname "$originalname")/$newname"
done

我在您提供的文件上对其进行了测试:

$ ./test.sh short.zhr 2&gt;/dev/null

   `short.zhr' -> `./MyName Sirname 2013-06-11'

你必须热爱 UNIX 哲学:)

对于你的情况,你可以运行

 ./test.sh somedir/AA*

C++ 版本

为了好玩,我写了一个 C++ 版本。这应该很容易移植。

它实际上更具可读性(除了格式化时间戳的部分......)。

#include <string>
#include <vector>
#include <fstream>
#include <ctime>
#include <cstdlib>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <iostream>

std::string extract_string(std::istream& is) {
    char len;
    if (is && is.read(&len, 1)) {
        std::string result(len, '\0');
        is.read(&*result.begin(), len);
        return result;
    }
    return "";
}

std::string timestamp(std::string const& fname, const char* fmt = "%Y-%m-%d")
{
    struct stat sb;
    if (-1 == stat(fname.c_str(), &sb))
        perror("cannot get file stats");

    if (struct tm* tmp = localtime(&sb.st_ctime))
    {
        std::string buf(200, '\0');
        buf.resize(strftime(&*buf.begin(), buf.size(), fmt, tmp));
        return buf;
    } else
        perror("localtime failed");
    return "";
}

int main(int argc, const char *argv[])
{
    for (int i = 1; i<argc; ++i)
    {
        const std::string fname(argv[i]);
        std::ifstream stream(fname.c_str(), std::ios::binary);

        stream.seekg(2085);
        std::string first  = extract_string(stream);
        std::string second = extract_string(stream);

        std::string newname = first + " " + second + " " + timestamp(fname);
        std::cout << (("rename \"" + fname + "\" \""  + newname + "\"").c_str());
    }
}

你会以完全相同的方式使用它。当然,您可以将其 print 改为 newname,并在您自己的脚本中使用它。 Edit 编辑版本以进行交叉编译赢exe。让它打印一个rename 命令。

【讨论】:

  • 这是医疗文件 - 24 小时心电图(即 HOLTER ECG)。文件名对于“安全性”是随机的,但对于存储/搜索来说是不切实际的,而且标题甚至没有像你看到的那样被加密。感谢您的输入,我会在家里尝试,但这里需要在 Windows 上运行。
  • 我在 Windows 上制作的。我在工作,使用cygwin.com bash,不过其他windows 的bash/od/date/dd 端口就足够了。
  • @Sehe 会更好地解释这一点
  • @MrGray 添加了 c++ 版本。这应该可以轻松编译为 Windows 可执行文件。
  • @MrGray 如果您信任“来自网络的随机可执行文件”,您可以尝试 rename-holter-ecg.exe,我在我的家庭 linux 机器上使用 mingw32 进行了交叉编译。 (在winehq.org下工作)
猜你喜欢
  • 2014-09-11
  • 2018-07-16
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-30
相关资源
最近更新 更多