2

I have a series of files that have two number in the first line (i.e. 170 1742) and then several other lines. If I want to just replace, in every file, the first number for its half (i.e. 85 1742) how would I do that in Unix?

terdon
  • 242,166
  • 1
    post some input fragments (from several files) and desired result – RomanPerekhrest Jun 05 '17 at 07:55
  • 4
    How may those numbers be expressed? Integer only, or can things like 23.45 or 3.2e9 or even 0x22 be found? Assuming positive decimal integer only, what should happen for odd numbers (should we get 7/2 = 3 (integer) or 7/2 = 3.5)? – Stéphane Chazelas Jun 05 '17 at 08:18
  • Whilst you could do this with Sed, the division will be much easier with Awk or Perl... – Toby Speight Jun 05 '17 at 13:17

3 Answers3

4
perl -pi -e 's|\d+|$&/2|e if $. == 1; close ARGV if eof' ./*.txt

Would replace the first sequence of decimal digits on the first line of every non-hidden txt file in the current directory, with the canonical decimal representation of half the corresponding number (3 for 006, 3.5 for 007 for instance).

If the number is very big (like greater than 1e20), perl may switch to engineering notation (5e+19).

Obviously that approach is only valid for decimal integer numbers. When applied to .1 it would give .5 instead of 0.05; when applied to 1.5, it would give 0.5.5 instead of 0.75; when applied to 0x10, it would give 0x10 (only 0 is halved) instead of 8 or 0x8 and so on.

If you wanted to handle decimal numbers in any notation (1, 010 (meaning 10, not octal 8), -1.123, 2.23e-4, inf, infinity, NaN...)), you'd need to adapt the matching regular expression, like:

s{(\d*\.\d+|\d+\.?)(e[-+]?\d+)?|nan|inf(inity)?}{$&/2}ie

Or make some assumption on where the number is on the line, like if it's the first sequence of non-spacing characters:

s{\S+}{$&/2}e
  • close ARGV can also be written as $. = 0 with the same condition upon eof –  Jun 05 '17 at 13:48
  • Also the regex would miss out floats of the form: mmm.nnn. We can write it as: (?:\d+(?:[.]\d*)?|[.]\d+)(?:e[-+]?\d+)? to make it water tight. –  Jun 05 '17 at 13:57
  • @Rakesh, why would it miss them out? Or maybe you're refering to an older version of my answer where I had \d|\d*\.\d+ forgetting that perl RE as opposed to ERE don't go for the longest match in alternations but give preference to the left-most ones. – Stéphane Chazelas Jun 05 '17 at 14:15
  • Oops, my wording is bad: I meant it would miss out mmm. type of floats, e.g., 534. –  Jun 05 '17 at 14:24
  • @Rakesh, ah OK thanks. I've now addressed it by adding a \.? on the \d+ part. – Stéphane Chazelas Jun 05 '17 at 15:35
3

Something like this?

awk 'FNR==1{print $1/2,$2;next}1' ./*.txt #or /directory/* or just file.txt

Or even

awk 'FNR==1{$1=$1/2}1' file1

Above awk will print the corresponding results on your screen or you can redirect the modified output to a new file using >newfile

With GNU awk , you can apply changes directly in your original file using gnu awk inplace option:

awk -i inplace 'FNR==1{$1=$1/2}1' file1

If your awk does not support inplace , you can do it manually like

 awk 'FNR==1{$1=$1/2}1' file > newfile && mv newfile file

This is actually what awk (and even sed) inplace editing does under the hood.

  • GNU sed -i or awk -i inplace (or perl -i which is the one that started it) would try to copy some of the file's metadata to the newfile though. – Stéphane Chazelas Jun 05 '17 at 12:06
  • @StéphaneChazelas GNU sed man pages makes no reference about this metadata capability. But if you have checked it , ok. https://www.gnu.org/software/sed/manual/sed.html?--in-place#index-GNU-extensions_002c-in_002dplace-editing – George Vasiliou Jun 05 '17 at 12:12
  • Without it, the permissions of the new file for instance would be based on the current umask instead of being copied from the original file. So you'd potentially be widening access to the file which could be a serious issue (there still are issues for attributes that sed can't restore like ownership, or when the file is a symlink) – Stéphane Chazelas Jun 05 '17 at 12:25
  • Also beware using gawk -i inplace like that introduces a security vulnerability unless you modify $AWKPATH not to include . or make sure you run that command from within a working directory where nobody could create a file called inplace or inplace.awk. – Stéphane Chazelas Jun 24 '23 at 21:16
0
tempd=$(mktemp -d)
for f in ./*.csv
do
   < "$f" \
       sed -e '1y/\t-/ _/; 1!s/.*/[&]/' |
       dc -e '
       [q]sq
       [?z0=q pc z0=?]s? # macro "?" to print line
       ?r2/ n32anpc l?x  # 1st line div/2 the last stack elem print it+spc+2nd
      ' \
   > "$tempd/$f"
   mv "$tempd/$f" .
done
rm -r "$tempd"