9

I'm trying to use something along the lines of

unexpand -t 4 *.php

but am unsure how to write this command to do what I want.

Weirdly,

unexpand -t 4 file.php > file.php

gives me an empty file. (i.e. overwriting file.php with nothing)

I can specify multiple files okay, but don't know how to then overwrite each file.

I could use my IDE, but there are ~67000 instances of to be replaced over 200 files, and this will take a while.

I expect that the answers to my question(s) will be standard unix fare, but I'm still learning...

unwind
  • 391,730
  • 64
  • 469
  • 606
jezmck
  • 1,138
  • 3
  • 18
  • 38

2 Answers2

12

You can very seldom use output redirection to replace the input. Replacing works with commands that support it internally (since they then do the basic steps themselves). From the shell level, it's far better to work in two steps, like so:

  1. Do the operation on foo, creating foo.tmp
  2. Move (rename) foo.tmp to foo, overwriting the original

This will be fast. It will require a bit more disk space, but if you do both steps before continuing to the next file, you will only need as much extra space as the largest single file, this should not be a problem.

Sketch script:

for a in *.php
do
  unexpand -t 4 $a >$a-notab
  mv $a-notab $a
done

You could do better (error-checking, and so on), but that is the basic outline.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • 4
    For paranoia's sake I tend to do: unexpand -t 4 "$a" >"$a"-notab && mv "$a"-notab "$a" (which is a lot of safety for a couple of extra characters). Also, the quotes are a *really* *good* *idea*. – ijw Jul 07 '09 at 23:50
  • Thanks to you both, very helpful. Is there an easy way to make the file match recursive? – jezmck Jul 09 '09 at 10:30
  • Making the file match recursive: try `for a in $(find .)`. This will complicate the logic of the subsequent commands, since `$a` will now include directory separators which can't be wrapped _en masse_ with quotes. You'll likely have to use `dirname` and `basename` inside the loop to assemble the source and target filenames. However, by using `find`, you also gain powerful filtering options. – Tom Aug 29 '18 at 18:52
2

Here's the command I used:

for p in $(find . -iname "*.js")
do
    unexpand -t 4 $(dirname $p)/"$(basename $p)" > $(dirname $p)/"$(basename $p)-tab"
    mv $(dirname $p)/"$(basename $p)-tab" $(dirname $p)/"$(basename $p)"
done

This version changes all files within the directory hierarchy rooted at the current working directory.

In my case, I only wanted to make this change to .js files; you can omit the iname clause from find if you wish, or use different args to cast your net differently.

My version wraps filenames in quotes, but it doesn't use quotes around 'interesting' directory names that appear in the paths of matching files.

To get it all on one line, add a semi after lines 1, 3, & 4.

This is potentially dangerous, so make a backup or use git before running the command. If you're using git, you can verify that only whitespace was changed with git diff -w.

Tom
  • 8,509
  • 7
  • 49
  • 78