将dd命令的输出读入到shell脚本变量中

3

作为一个对shell脚本非常陌生的人,我拼凑了下面的代码来逐个扇区搜索/dev/sdd1以查找字符串。如何将扇区数据获取到$HAYSTACK变量中?

#!/bin/bash

HAYSTACK=""
START_SEARCH=$1
NEEDLE=$2
START_SECTOR=2048
END_SECTOR=226512895+1
SECTOR_NUMBER=$((START_SEARCH + START_SECTOR))
while [  $SECTOR_NUMBER -lt $END_SECTOR ]; do
    $HAYSTACK=`dd if=/dev/sdd1 skip=$SECTOR_NUMBER count=1 bs=512`
    if [[ "$HAYSTACK" =~ "$NEEDLE" ]]; then
        echo "Match found at sector $SECTOR_NUMBER"
        break
    fi
    let SECTOR_NUMBER=SECTOR_NUMBER+1 
done

更新

本意并不是要编写一个完美的脚本来处理碎片文件的情况(我觉得这几乎是不可能的)。在我的情况下,无法区分带有空值的字符串也不是什么问题。

如果您能将管道建议扩展为答案,那就足够了。谢谢!

背景

我设法清除了我的www文件夹,并一直试图尽可能多地恢复我的源文件。我使用Scalpel恢复了我的php和html文件。但是我在Ubuntu 16.04上能够工作的版本是版本1.60,它不支持头/尾部正则表达式,因此我无法为css、js和json文件制定良好的模式。

我记得有一些罕见的字符串可以搜索并找到我的文件,但不知道字符串在块中的位置。我想出的解决方案是编写这个shell脚本来从分区读取块并查找子字符串,如果找到匹配项,则打印LSB号码并退出。


2
个人而言,我倾向于在这里使用非 shell 语言——bash 仅支持 C 字符串,并且没有本地类型能够表示文字 NUL 值(除非进行某些 hackery,例如(滥用数组来实现此目的)。[[ $value =~ $re ]] 方法永远无法区分 needlen<NUL>e<NUL>e<NUL><NUL>dle - Charles Duffy
2
如果$NEEDLE穿过块边界,这种方法将无法找到它。 - jurez
3
顺便提一句,你需要将 $HAYSTACK=... 更改为 HAYSTACK=...;这是 http://shellcheck.net/ 可以自动识别的一个 bug 类型。 - Charles Duffy
1
(顺便说一下 - 使用全大写的变量名称用于具有操作系统或shell意义的变量; 对自己的变量使用小写名称可以确保不会错误地覆盖POSIX定义工具的某些含义。请参见http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html,第四段)。 - Charles Duffy
1
现在不是写完整答案的好时机 - 我的注意力在另一个窗口上 - 但是稍微详细地概括一下:exec 3< <(dd if=/dev/sdd1 | tr '\0' '@'); sector_count=0; while IFS= read -r -N 2048 -d '' sector <&3; do (( ${#sector} == 2048 )) || { echo "Got a short read (${#sector} bytes) at sector $sector_count; aborting!"; exit 1; }; [[ $sector =~ "$needle" ]] && echo "Found needle at $sector_count"; (( ++sector_count )); done - Charles Duffy
显示剩余14条评论
2个回答

2
  1. If the searched for item is a text string, consider using the -t option of the strings command to print the offset of where the string is found. Since strings doesn't care where the data is from, it works on files, block devices, and piped input from dd.

    Example from the start of a hard disk:

    sudo strings -t d /dev/sda | head -5
    

    Output:

        165 ZRr=
        286 `|f 
        295 \|f1
        392 GRUB 
        398 Geom
    

    Instead of head that could be piped to grep -m 1 GRUB, which would output only the first line with "GRUB":

    sudo strings -t d /dev/sda | grep -m 1 GRUB
    

    Output:

        392 GRUB 
    

    From there, bash can do quite a lot. This code finds the first 5 instances of "GRUB" on my boot partition /dev/sda7:

    s=GRUB ; sudo strings -t d /dev/sda7 | grep "$s" | 
    while read a b ; do
        n=${b%%${s}*}
        printf "String %-10.10s found %3i bytes into sector %i\n" \
             "\"${b#${n}}\"" $(( (a % 512) + ${#n} )) $((a/512 + 1)) 
    done | head -5
    

    Output (the sector numbers here are relative to the start of the partition):

    String "GRUB Boot found   7 bytes into sector 17074
    String "GRUB."    found 548 bytes into sector 25702
    String "GRUB."    found 317 bytes into sector 25873
    String "GRUBLAYO" found 269 bytes into sector 25972
    String "GRUB"     found 392 bytes into sector 26457
    

    Things to watch out for:

    • Don't do dd-based single-block searches with strings as it would fail if the string spanned two blocks. Use strings to get the offset first, then convert that offset to blocks, (or sectors).

    • strings -t d can return big strings, and the "needle" might be several bytes into a string, in which case the offset would be the start of the big string, rather than the grep string (or "needle"). The above bash code allows for that and uses the $n to calculate a corrected offset.

  2. Lazy all-in-one util rafind2 method. Example, search for the first instance of "GRUB" on /dev/sda7 as before:

    sudo rafind2 -Xs GRUB /dev/sda7 | head -7
    

    Output:

    0x856207
    - offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
    0x00856207  4752 5542 2042 6f6f 7420 4d65 6e75 006e  GRUB Boot Menu.n
    0x00856217  6f20 666f 6e74 206c 6f61 6465 6400 6963  o font loaded.ic
    0x00856227  6f6e 732f 0069 636f 6e64 6972 0025 733a  ons/.icondir.%s:
    0x00856237  2564 3a25 6420 6578 7072 6573 7369 6f6e  %d:%d expression
    0x00856247  2065 7870 6563 7465 6420 696e 2074        expected in t 
    

    With some bash and sed that output can be reworked into the same format as the strings output:

    s=GRUB ; sudo rafind2 -Xs "$s" /dev/sda7 | 
    sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" | 
    sed -r -n 'h;n;n;s/.{52}//;H;n;n;n;n;g;s/\n//p' | 
    while read a b ; do
       printf "String %-10.10s\" found %3i bytes into sector %i\n"  \
              "\"${b}" $((a%512)) $((a/512 + 1)) 
    done | head -5
    

    The first sed instance is borrowed from jfs' answer to "Program that passes STDIN to STDOUT with color codes stripped?", since the rafind2 outputs non-text color codes.

    Output:

    String "GRUB Boot" found   7 bytes into sector 17074
    String "GRUB....L" found  36 bytes into sector 25703
    String "GRUB...LI" found 317 bytes into sector 25873
    String "GRUBLAYO." found 269 bytes into sector 25972
    String "GRUB .Geo" found 392 bytes into sector 26457
    

array=( $(some-command) ) 是不好的写法 -- 参见BashPitfalls #50。不过,除此之外,这是一个很好的答案。 - Charles Duffy
@CharlesDuffy,谢谢,即使没有错误的形式,如果有空格,数组也会有两个以上的项,这已经够糟糕了。 - agc
假设我们的搜索目标足够罕见,没有产生大量匹配项,但其第一个出现位置在 100GB 分区的中间某处。在幕后会发生什么?它是否内部使用批处理和垃圾回收,或者我们需要大量内存才能使其正常工作? - Majid Fouladpour
@MajidFouladpour,dd 输出二进制数据,但 grep 需要文本输入,因此需要在两者之间加入一些东西。建议将测试更改为s=someveryrarestringonlyfoundonceandnomore; sudo strings -t d /dev/sda | grep "$s" | while read a b; do n=${b%%${s}*}; printf "String %-10.10s found %3i bytes into sector %i\n" "\"${b#${n}}\"" $(( (a % 512) + ${#n} )) $((a/512 + 1)); done | head -1以获得更易读的输出。 - agc
三倍的性能提升非常令人向往。那么,让我们进行一次思想实验。我想象strings每次从块中以批处理方式读取二进制数据,每次增加一个偏移量,然后尝试识别数据中的text,然后编译一个带有扇区号和相关文本的列表,然后将其传输到grep。难道不能将needle转换为二进制数据,然后使用dd进行比较吗?即使要求用户事先手动进行转换也不会过分,考虑到性能提升。 - Majid Fouladpour
显示剩余4条评论

1
你有没有考虑过类似这样的东西?
cat /dev/sdd1 | od -cv | sed s'/[0-9]* \(.*\)/\1/' | tr -d '\n' | sed s'/  F   l/  F   l/'g  > v1
cat /dev/sdd1 | od -cv | sed s'/[0-9]* \(.*\)/\1/' | tr -d '\n' | sed s'/  F   l/x F   l/'g  > v2
cmp -lb v1 v2

例如将此应用于一个.pdf文件。
od -cv phase-2-guidance.pdf  | sed s'/[0-9]* \(.*\)/\1/' | tr -d '\n' | sed s'/  F   l/  F   l/'g  > v1
od -cv phase-2-guidance.pdf  | sed s'/[0-9]* \(.*\)/\1/' | tr -d '\n' | sed s'/  F   l/  x   l/'g  > v2
cmp -l v1 v2

给出输出

   228 106 F    170 x
 23525 106 F    170 x
 37737 106 F    170 x
 48787 106 F    170 x
 52577 106 F    170 x
 56833 106 F    170 x
 57869 106 F    170 x
118322 106 F    170 x
119342 106 F    170 x

第一列中的数字将是寻找模式开始的字节偏移量。这些字节偏移量乘以4,因为od每个字节使用四个字节。

在bash shell中的单行格式,无需编写大型临时文件,可以是:

od -cv phase-2-guidance.pdf  | sed s'/[0-9]* \(.*\)/\1/' | tr -d '\n' | sed s'/  F   l/  x   l/'g | cmp -lb - <(od -cv phase-2-guidance.pdf  | sed s'/[0-9]* \(.*\)/\1/' | tr -d '\n' | sed s'/  F   l/  F   l/'g )

这样可以避免需要将/dev/sdd1的内容写入临时文件。
以下是一个示例,查找USB驱动器上的PDF文件,并除以4和512以获取块号。
dd if=/dev/disk5s1 bs=512 count=100000 | od -cv | sed s'/[0-9]* \(.*\)/\1/' | tr -d '\n'  | cmp -lb - <(dd if=/dev/disk5s1 bs=512 count=100000 | od -cv | sed s'/[0-9]* \(.*\)/\1/' | tr -d '\n' | sed s'/P   D   F/x   D   F/'g ) | awk '{print int($1/512/4)}' | head -10

测试这个会给出:

100000+0 records in
100000+0 records out
51200000 bytes transferred in 18.784280 secs (2725683 bytes/sec)
100000+0 records in
100000+0 records out
51200000 bytes transferred in 40.915697 secs (1251353 bytes/sec)
cmp: EOF on -
28913
32370
32425
33885
35097
35224
37177
38522
39981
41570

这里的数字是512字节块的编号。检查结果如下:

dd if=/dev/disk5s1 bs=512 skip=35224 count=1 | od -vc | grep P

0000340   \0  \0  \0 001   P   D   F       C   A   R   O  \0  \0  \0  \0

这是一个实际的完整示例,使用磁盘并查找字符序列live,其中字符由NUL分隔。
   dd if=/dev/disk5s1 bs=512 count=100000 | od -cv | sed s'/[0-9]* \(.*\)/\1/' | tr -d '\n' | sed s'/l  \\0   i  \\0   v  \\0   e/x  \\0   i  \\0   v  \\0   e/'g | cmp -lb - <(dd if=/dev/disk5s1 bs=512 count=100000 | od -cv | sed s'/[0-9]* \(.*\)/\1/' | tr -d '\n' | sed s'/l  \\0   i  \\0   v  \\0   e/l  \\0   i  \\0   v  \\0   e/'g )

注意

  • 这不会处理将模式分成非连续块的情况,其中该块会被分割。第二个sed,它执行模式和替换,可以被自定义程序所取代,该程序进行一些部分模式匹配,并在匹配字符数超过某个级别时进行替换。这可能会返回错误的结果,但可能是处理碎片化的唯一方法。

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