21

Context

I have a 16 GB SD card with a Linux based OS for a Raspberry Pi. Most of the space is empty.

I want to share the SD .img with other people but if I use the command

dd if=/dev/sdXX of=/home/user123/SD.img

it will create a 16 GB image. Too big.

Question

How can I re-size a 16GB SD card image into a smaller 4GB?

I have tried with GParted: it creates a partition with 4GB with no problem, however the whole .img of the SD card continues to be 16 GB with 12 GB of unallocated space.

GParted table

I have read the question and answer Cloning multiple partitions in Ubuntu, but I still cannot re-size the 16GB SD card into a 4GB one.

More info

~$ lsblk 
...

sdc      8:32   1  14,9G  0 disk 
├─sdc1   8:33   1   100M  0 part 
└─sdc2   8:34   1     4G  0 part 
~$ sudo fdisk -l /dev/sdc
Disk /dev/sdc: 14,9 GiB, 15931539456 bytes, 31116288 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xf8a631ce

Device     Boot  Start     End Sectors  Size Id Type
/dev/sdc1  *      2048  206847  204800  100M  c W95 FAT32 (LBA)
/dev/sdc2       206848 8595455 8388608    4G 83 Linux

Any advice is appreciated!

Please note: as observed by Melebius in a comment, the right word to use is shrink:

You cannot resize an SD card as it is hardware with a given capacity that cannot be changed. You clearly want to shrink an SD card image.

6
  • 2
    have a look at resize2fs command
    – marosg
    Sep 16, 2019 at 9:33
  • Possible duplicate of Cloning multiple partitions in Ubuntu
    – Melebius
    Sep 16, 2019 at 9:50
  • @guiverc, thanks for pointing out it. It is true that the OS info is unnecessary for the question.
    – Leos313
    Sep 16, 2019 at 9:54
  • 1
    @Melebius, I think that cloning multiple partitions could be related with the problem of this question but, to be honest, the real problem is another one: to shrink an SD CARD (even if it has just one partition). Does it make sense? The key is the truncat command and the use of GParted. Of course we can use GParted to handle multiple partitions but the trick works also with just one. Moreover, the use of truncat is never mentioned in the other answers
    – Leos313
    Sep 18, 2019 at 17:02
  • 1
    @Melebius, thank you. However using the word "shrink" on google will guide you on many other solutions, all working perfectly. The problem, for me, was to find the right word to insert in the google search engine. I was looking for re-size that, clearly, was not the right word to use. I suggest keeping, in some way, the word 're-size' together with the right therminology. Do you agree? I have added a please note at the end of the question.
    – Leos313
    Sep 19, 2019 at 10:23

5 Answers 5

30

This article gives a solution that solves my problem (*). It is quite similar to the other one, but it better explains how to calculate and which meaning have the numbers and the partitions.

The key information was the use of the command truncate. Following the full solution in order to not lose the answer.

A preliminary step consists in cloning the SD card in your PC:

  1. use lsblk to see which devices are available and if their partitions are mounted

  2. unmount all partitions of the device you want to copy on your pc. For example:

     umount /dev/sdc1
     umount /dev/sdc2
    
  3. create a copy of the whole sd card with all the partitions unmounted

     dd if=/dev/sdc of=/path/to/file/myimage.img
    

Shrinking images on Linux

Context of the problem:

Having a myimage.img bigger then the hardware support (if it is smaller there should be no problem; however, using the same strategy, you can better fit the image in the hardware support).

The secret is to use standard Linux tools and instruments: GParted, fdisk and truncate.

Requirements:

  • A Linux PC
  • The .img you want to shrink (myimage.img in this example)

Creating loopback device:

GParted is an application typically used to manage partition tables and filesystems. In order to shrink the image, GParted is going to be used along the first part of the answer.

GParted operates on devices, not simple files like images. This is why we first need to create a device for the image. We do this using the loopback-functionality of Linux.

Let's enable enable the loopback:

sudo modprobe loop

Let's request a new (free) loopback device:

sudo losetup -f

The command returns the path to a free loopback device:

/dev/loop0

Let's create a device of the image:

sudo losetup /dev/loop0 myimage.img

The device /dev/loop0 represents myimage.img. We want to access the partitions that are on the image, so we need to ask the kernel to load those too:

sudo partprobe /dev/loop0

This should give us the device /dev/loop0p1, which represents the first partition in myimage.img. We do not need this device directly, but GParted requires it.

Resize partition using GParted:

Let's load the new device using GParted:

sudo gparted /dev/loop0

When the GParted application opens, it should appear a window similar to the following:

screenshot

Now notice a few things:

  • There is one partition.
  • The partition allocates the entire disk/device/image.
  • The partition is filled partly.

We want to resize this partition so that is fits its content, but not more than that.

Select the partition and click Resize/Move. A window similar to the following will pop up:

screenshot of dialog

Drag the right bar to the left as much as possible.

Note that sometimes GParted will need a few MB extra to place some filesystem-related data. You can press the up-arrow at the New size-box a few times to do so. For example, I pressed it 10 times (=10MiB) for FAT32 to work. For NTFS you might not need to at all.

Finally press Resize/Move. You will return to the GParted window. This time it will look similar to the following:

unallocated space on right

Notice that there is a part of the disk unallocated. This part of the disk will not be used by the partition, so we can shave this part off of the image later. GParted is a tool for disks, so it doesn't shrink images, only partitions, we have to do the shrinking of the image ourselves.

Press Apply in GParted. It will now move files and finally shrink the partition, so it can take a minute or two, but most of the time it finishes quickly. Afterwards close GParted.

Now we don't need the loopback-device anymore, so unload it:

sudo losetup -d /dev/loop0

Shaving the image:

Now that we have all the important data at the beginning of the image it is time to shave off that unallocated part. We will first need to know where our partition ends and where the unallocated part begins. We do this using fdisk:

fdisk -l myimage.img

Here we will see an output similar to the following:

Disk myimage.img: 6144 MB, 6144000000 bytes, 12000000 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000ea37d

      Device Boot      Start         End      Blocks   Id  System
myimage.img1            2048     9181183     4589568    b  W95 FAT32

Note two things in the output:

  • The partition ends on block 9181183 (shown under End)
  • The block-size is 512 bytes (shown as sectors of 1 * 512)

We will use these numbers in the rest of the example. The block-size (512) is often the same, but the ending block (9181183) will differ for you. The numbers mean that the partition ends on byte 9181183512 of the file. After that byte comes the unallocated-part. Only the first 9181183512 bytes will be useful for our image.

Next we shrink the image-file to a size that can just contain the partition. For this we will use the truncate command (thanks uggla!). With the truncate command need to supply the size of the file in bytes. The last block was 9181183 and block-numbers start at 0. That means we need (9181183+1)*512 bytes. This is important, else the partition will not fit the image. So now we use truncate with the calculations:

truncate --size=$[(9181183+1)*512] myimage.img

(*) it seems that the original post by FrozenCow has been moved here.

2
  • The SD card is not defragemented. You only cut from the end where something was written last. This can be a few MB, although several GB would actually be free on the SD card. These would be skipped with your procedure.
    – Micha93
    Feb 11, 2021 at 9:33
  • @Micha93: I guess you meant that your image has two partitions, called xxx.img1 and xxx.img2 by fdisk. As shown the first image occupies sectors 8192 to 93802, and the second is at sectors 98304 to 30375935. For this example you should use the End sector of the last partition, so 30375935: truncate --size=$[(30375935+1)*512] xxx.img
    – cootje
    Jul 13, 2021 at 16:21
3

You can make use of the options bs and count in the dd-command to limit the size of the output file.

Example:

dd if=sdx of=SD.img bs=1G count=4

would result in an outputfile with a size of 4 GiB.

Take a deep look into man dd.

You'd need to know how many bytes you have to copy so that all partitions are fully covered, so take a look with sudo fdisk -l /dev/sdx which sector is the last one you need.

The partitions need to be at the start of the disk (like in the picture you provided).

Disks with msdos-partition-table can be cloned easily this way, but if the disk uses GPT and is to be cloned to a disk with different size, the protective MBR needs to be adapted afterwords and the GPT-backup which resides at the very end of the disk needs to be recreated, this can be done with gdisk.


From your fdisk-output you can see that the last sector of the last partition is sector 8595455, that means you have to copy at least 8595455+1 sectors (first sector is 0). With a sector-size of 512 bytes this is equal to 4,400,873,472 bytes. bs multiplied with count have to be greater or equal than this.

Maybe this is still too big for a 4GB USB-stick, you can still reduce the size of sdc2, there's plenty of unused space in it.

For the current example you provided,

 dd if=/dev/sdc of=SD.img bs=10M count=420

will cover the partition-table, sdc1 and sdc2. Calculate:

10*1024*1024*420 = 4,404,019,200 > 4,400,873,472
3
  • testing the solution. One question: what do you mean with "You'd need to know how many bytes you have to copy so that all partitions are fully covered, so take a look with sudo fdisk -l /dev/sdx which sector is the last one you need. The partitions need to be at the start of the disk (like in the picture you provided)". Also I am updating the question with the output of the command you were speaking about.
    – Leos313
    Sep 16, 2019 at 10:14
  • You don't want to clone empty space, that's why the partitions should be at the start of the disk. And you don't want to clone a part of a partition (file-system), that's why you need to know on which sector the last partition on the disk ends.
    – mook765
    Sep 16, 2019 at 10:19
  • this was the solution I have founded, detailed with commands and images and it works. It is a similar answer but uses, also truncate and a loopback device. It works properly. softwarebakery.com//shrinking-images-on-linux Once also this answer is update I will vote up, it was the key to find the solution
    – Leos313
    Sep 16, 2019 at 10:35
3

resize2fs can also be used to resize that.

sudo resize2fs -fp SD.img 4G

It also resizes the file itself!

2
  • 2
    Sounded promising but did not work for me: "Wrong magic numer" & "bad superblock" perhaps cause I have mroe than one partition in there? The longer way worked though.
    – Mr.Gosh
    Oct 29, 2021 at 12:02
  • @Mr.Gosh Sure, that .img looks like a partition/filesystem image and not a full SD card image
    – golimar
    May 9 at 6:18
3

The selected answer applies perfectly for dos disklabel type. For GPT type, one needs to consider adding 33 sectors as GPT stores a table also at the end of the disk.

So for GPT users, 'truncate' commands needs to look like this:

truncate --size=$[(End_of_last_partition+1+33)*512] myimage.img

This should produce a GPT error in fdisk -l. To fix this, run following:

gdisk myimage.img

Run command to verify the disk: v. You should see some errors found on the disk.

"In gdisk, you would type x to enter the experts' menu, then type e to move the backup partition table data to the new end of the disk, and then type w to write the changes to disk." [Source] how to truncate a disk image file of unused space without corrupting GPT partition table (end pointer) - Thanks!

2

Looked through many of the methods here and I understood all of them but the steps were something I wasn't looking forward to do in having to execute them in turn to simply shrink a Raspberry Pi IMG file down. So looking I came across this Bash script from a project called PiShrink.

It was quite trivial to execute and it worked on my MacOS M1 Mac Mini to boot.

Backstory

In my particular case I had 2 32GB SD cards and for whatever reason 1 was slightly (~95MB) smaller than the other and balenaEtcher refused to write the IMG from what seemed to be the larger of the 2 SD cards to the smaller.

I knew I was only using ~10GB of the 32GB so I could shrink the IMG file down to fit.

Steps

To start I had the following files. The .zip file contained the .img file but I'm showing it unzipped from it below.

$ ls -lh | grep -E 'img|zip'
-rw-r--r--  1 root staff  28G Jan 29 13:16 backup.zip
--w--wx-w-  1 slm  staff  30G Jan  1  1980 disk.img

I then downloaded the PiShrink shell script and made it executable:

$ wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
$ chmod +x pishrink.sh

I then ran it like so:

$ sudo pishrink disk.img pi.img
Copying disk.img to pi.img...
e2fsck 1.44.0 (7-Mar-2018)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
STORAGE: 69402/7684096 files (0.2% non-contiguous), 9562823/30732288 blocks
resize2fs 1.44.0 (7-Mar-2018)
resize2fs 1.44.0 (7-Mar-2018)
Resizing the filesystem on /dev/disk4s2 to 9273700 (1k) blocks.
Begin pass 2 (max = 245358)
Relocating blocks             XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Begin pass 3 (max = 3752)
Scanning inode table          XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Begin pass 4 (max = 3420)
Updating inode references     XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
The filesystem on /dev/disk4s2 is now 9273700 (1k) blocks long.

"disk4" ejected.
fdisk: could not open MBR file /usr/standalone/i386/boot0: No such file or directory
Enter 'help' for information
fdisk: 1>          Starting       Ending
 #: id  cyl  hd sec -  cyl  hd sec [     start -       size]
------------------------------------------------------------------------
 2: 83 1023   3  32 - 1023 254   2 [   1056768 -   61464576] Linux files*
Partition id ('0' to disable)  [0 - FF]: [83] (? for help) Do you wish to edit in CHS mode? [n] Partition offset [0 - 62521344]: [1056768] Partition size [1 - 61464576]: [61464576] fdisk:*1> Writing MBR at offset 0.
fdisk: 1> Shrunk pi.img from 30G to 9.3G

The resulting IMG file is now ~9GBs.

$ ls -lh | grep -E 'img|zip'
-rw-r--r--  1 root staff  28G Jan 29 13:16 backup.zip
--w--wx-w-  1 slm  staff  30G Jan  1  1980 disk.img
--w---x---  1 root staff 9.4G Jan 29 17:04 pi.img

I then repackaged it as a ZIP file just so that it was kept in the most efficient size possible while dealing with it on disk.

$ sudo zip pi.zip pi.img

We now have the following set of files kicking around:

$ ls -lh | grep -E 'img|zip'
-rw-r--r--  1 root staff  28G Jan 29 13:16 backup.zip
--w--wx-w-  1 slm  staff  30G Jan  1  1980 disk.img
--w---x---  1 root staff 9.4G Jan 29 17:04 pi.img
-rw-r--r--  1 root staff 8.3G Jan 29 17:08 pi.zip

I can then use balenaEtcher or ApplePi-Baker to burn the ZIP file to the "smaller" SD card.

ss01

Poking at the SD card on my Mac I can see it's showing ~10GB.

$ diskutil list /dev/disk4
/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *31.9 GB    disk4
   1:             Windows_FAT_32 LIBREELEC               536.9 MB   disk4s1
   2:                      Linux                         10.0 GB    disk4s2
                    (free space)                         21.3 GB    -

NOTE: I could do the same commands from a Linux system using lsblk etc..

Post Ops

I did notice after using the above method that the filesystem within the SD card was short on the full 32GB. Mainly it showed like so:

$ df -h | grep -E "File|mmc"
Filesystem                Size      Used Available Use% Mounted on
/dev/mmcblk0p1          511.7M    141.7M    370.0M  28% /flash
/dev/mmcblk0p2            8.6G      8.2G    377.4M  96% /storage

This should be easily resolved by expanding the SD filesystem from within once you've booted it back up.

To start this I first use parted to extend the partition:

$ parted /dev/mmcblk0 resizepart 2 100%
Warning: Partition /dev/mmcblk0p2 is being used. Are you sure you want to
continue?
Yes/No? yes
yes
Information: You may need to update /etc/fstab.

I then rebooted the system:

$ reboot

After I then online expanded the filesystem using resize2fs:

$ resize2fs /dev/mmcblk0p2
resize2fs 1.45.6 (20-Mar-2020)
Filesystem at /dev/mmcblk0p2 is mounted on /storage; on-line resizing required
old_desc_blocks = 71, new_desc_blocks = 234
The filesystem on /dev/mmcblk0p2 is now 30638592 (1k) blocks long.

And it now shows all the free space on the SD card:

$ df -h | grep -E "File|mmc"
Filesystem                Size      Used Available Use% Mounted on
/dev/mmcblk0p1          511.7M    141.7M    370.0M  28% /flash
/dev/mmcblk0p2           28.3G      8.2G     20.1G  29% /storage

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.