42

I've got an extreme problem, and all of the solutions I can imagine are complicated. According to my UNIX/Linux experience there must be an easy way.

I want to delete the first 31 bytes of each file in /foo/. Each file is long enough. Well, I'm sure somebody will deliver me a suprisingly easy solution I just can't imagine. Maybe awk?

1
  • 2
    Any awk/sed/ed solution will be line-oriented, so if you don't know the first line will be at least 31 characters then complications ensue. Commented May 27, 2011 at 15:51

5 Answers 5

35
for file in /foo/*
do
  if [ -f "$file" ]
  then
    dd if="$file" of="$file.truncated" bs=31 skip=1 && mv "$file.truncated" "$file"
  fi
done

or the faster, thanks to Gilles' suggestion:

for file in /foo/*
    do
      if [ -f $file ]
      then
        tail +32c $file > $file.truncated && mv $file.truncated $file
      fi
    done

Note: Posix tail specify "-c +32" instead of "+32c" but Solaris default tail doesn't like it:

   $ /usr/bin/tail -c +32 /tmp/foo > /tmp/foo1
    tail: cannot open input

/usr/xpg4/bin/tail is fine with both syntaxes.

If you want to keep the original file permissions, replace

... && mv "$file.truncated" "$file"

by

... && cat "$file.truncated" "$file" && rm "$file.truncated"
12
  • 1
    Suggesting dd here is overkill, tail is more appropriate (simpler, less risk of a killer typo, no spurious messages on stderr). Commented May 27, 2011 at 22:16
  • You are right. I usually avoid commands intended to process text files when processing possibly binary ones but "tail +32c" will work here. Commented May 28, 2011 at 8:26
  • 1
    @jlliagre: You have written cut (shouldn't that be tail? ... asis, it doesn't work for me... Commented May 28, 2011 at 9:21
  • Of course, it's tail. Sorry about the mismatch. Commented May 28, 2011 at 9:23
  • 1
    mv changes the file permissions. you can use cat instead of mv like this: tail +32c $file > $file.truncated && cat $file.truncated > $file && rm $file.truncated @jlliagre Commented Aug 30, 2020 at 18:36
19

The following commands cut first 31 bytes from $file (using $file~ as a temp. copy):

dd if="$file" of="$file~" bs=1 skip=31
mv "$file~" "$file"

You only need to list or find all files under /foo/ and execute the two above for every $file found.

1
  • 4
    Swapping bs and skip values will increase the performance. Commented May 27, 2011 at 16:07
17

tail -c +32 outputs its input minus the first 31 bytes. (Yes, the argument is off by one.) To edit a file in place, use sponge in a loop, or if you don't have it and don't want to bother, do its job in the shell:

for x in /foo/*; do tail -c +32 "$x" | sponge "$x"; done
for x in /foo/*; do tail -c +32 "$x" >"$x.new" && mv "$x.new" "$x"; done

If the commands are interrupted for whatever reason (e.g. a power failure), it might be hard to figure out where you left off. Writing the new files to a separate directory would make things easier.

mkdir /foo.tmp
cd /foo
for x in *; do tail -c +42 -- "$x" >"/foo.tmp/$x" && rm -- "$x"; done
mv /foo.tmp/* /foo
rmdir /foo.tmp

If the files are really large (as in, large enough that having two copies of even a single one is a problem), you can use one of the techniques mentioned in this thread.

2

You can use Vim in Ex mode:

for each in /foo/*
do
  ex -sc '%!tail -c+32' -cx "$each"
done
  1. % select all lines

  2. ! run command

  3. x save and close

2

Using dd with block size 1 is extremely slow. It is possible to use a large block size while specifying the skip in bytes like this:

dd iflag=skip_bytes if=infile.txt of=outfile.txt skip=31 bs=1M

1
  • 1
    That is GNU dd only, BSD versions of dd do not have the iflag option. Commented Nov 2, 2022 at 23:57

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.