0

I have a lot of text files in which I would like to find the word 'CASE' and replace it with the related filename. I tried

find . -type f | while read file
do
awk '{gsub(/CASE/,print "FILENAME",$0)}' $file >$file.$$
mv $file.$$ >$file
done

but I got the following error

awk: syntax error at source line 1 context is >>> {gsub(/CASE/,print <<< "CASE",$0)}
awk: illegal statement at source line 1

I also tried

for i in $(ls *); 
do 
awk '{gsub(/CASE/,${i},$0)}' ${i} > file.txt; 
done

getting an empty output and

awk: syntax error at source line 1 context is >>> {gsub(/CASE/,${ <<<
awk: illegal statement at source line 1

Saagar Elias Jacky
  • 2,684
  • 2
  • 14
  • 28
user3184877
  • 155
  • 2
  • 9
  • 1
    Never do `for i in $(ls *)` as that adds nothing compared to `for i in *` and introduces possible error cases like files with spaces in their names. Also, awk is not shell - you cannot access a shell variable inside an awk script, just like you cannot access a shell variable inside a C program. See http://cfajohnson.com/shell/cus-faq-2.html#Q24 for how to pass the VALUE of a shell variable to an awk script. – Ed Morton Apr 28 '15 at 19:11

3 Answers3

2

Why awk? sed is what you want:

while read -r file; do 
    sed -i "s/CASE/${file##*/}/g" "$file"
done < <( find . -type f )

or

while read -r file; do 
    sed -i.bak "s/CASE/${file##*/}/g" "$file"
done < <( find . -type f )

To create a backup of the original.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • I'd probably fix the output from find rather than the variable expansion but . – Etan Reisner Apr 28 '15 at 19:15
  • 1
    I'm sure you would like to check [this](http://stackoverflow.com/q/29613304/171318) :) – hek2mgl Apr 28 '15 at 19:22
  • @EdMorton, no you did it, you left me scratching my head? The read command is reading filenames, not the file itself and I can't see that requiring altering `IFS` to prevent stripping leading/trailing whitespace. The `sed` command will preserve the whitespace. Where is it you see stripping occurring? – David C. Rankin Apr 28 '15 at 19:36
  • 1
    That's ok, I still have a bit of hair left, so scratching my head does no harm. It certainly helps me think back through the problem more closely `:p` – David C. Rankin Apr 28 '15 at 19:43
2

You didn't post any sample input and expected output so this is a guess but maybe this is what you want:

find . -type f |
while IFS= read -r file
do
    awk '{gsub(/CASE/,FILENAME)} 1' "$file" > "${file}.$$" &&
    mv "${file}.$$" "$file"
done

Every change I made to the shell code is important so if you don't understand why I changed any part of it, ask the question.

btw if after making the changes you are still getting the error message:

awk: syntax error at source line 1
awk: illegal statement at source line 1

then you are using old, broken awk (/usr/bin/awk on Solaris). Never use that awk. On Solaris use /usr/xpg4/bin/awk (or nawk if you must).

Caveats: the above will fail if your file name contains newlines or ampersands (&) or escaped digits (e.g. \1). See Is it possible to escape regex metacharacters reliably with sed for details. If any of that is a problem, post some representative sample input and expected output.

Community
  • 1
  • 1
Ed Morton
  • 188,023
  • 17
  • 78
  • 185
1

print in that first script is the error.

The second argument to gsub is the replacement string not a command.

You want just FILENAME. (Note not "FILENAME" that's a literal string. FILENAME the variable.)

find . -type f -print0 | while IFS= read -d '' file
do
    awk '{gsub(/CASE/,FILENAME,$0)} 7' "$file" >"$file.$$"
    mv "$file.$$" "$file"
done

Note that I quoted all your variables and fixed your find | read pipeline to work correctly for files with odd characters in the names (see Bash FAQ 001 for more about that). I also fixed the erroneous > in the mv command.

See the answers on this question for how to properly escape the original filename to make it safe to use in the replacement portion of gsub.

Also note that recent (4.1+ I believe) versions of awk have the -i inplace argument.

To fix the second script you need to add the quotes you removed from the first script.

for i in *; do awk '{gsub(/CASE/,"'"${i}"'",$0)}' "${i}" > file.txt; done

Note that I got rid of the worse than useless use of ls (worse than useless because it actively breaks files with spaces or shell metacharacters in the their names (see Parsing ls for more on that).

That command though is somewhat ugly and unsafe for filenames with various characters in them and would be better written as the following though:

for i in *; do awk -v fname="$i" '{gsub(/CASE/,fname,$0)}' "${i}" > file.txt; done

since that will work with filenames with double quotes/etc. in their names correctly whereas the direct variable expansion version will not.

That being said the corrected first script is the right answer.

Community
  • 1
  • 1
Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • `/CASE/,"${i}",$0` wouldn't work as you'd get the literal string `${i}`. You'd instead need something like `/CASE/,"'"$i"'",$0` but I wouldn't do it as it's still open to insidious breakage. Also, `$0` is the default 3rd arg for `*sub()` so you don't need to specify it. – Ed Morton Apr 28 '15 at 19:15
  • @EdMorton Point. I'd assumed double quotes there. Fixing. – Etan Reisner Apr 28 '15 at 19:17
  • 1
    @EtanReisner I'm sure you would like to check [this](http://stackoverflow.com/q/29613304/171318) :) (Sorry, for posting it three times. I just wanted to be fair) – hek2mgl Apr 28 '15 at 19:22
  • @hek2mgl Indeed. I favorited that question when it was happening so I'd have it for reference but didn't think about it here. – Etan Reisner Apr 28 '15 at 19:28
  • Don't worry, I've added that just for fun! :) Who minds that when typing a command? ... For a shell application/framework it might be good to have (at)mkelement's answer it mind and create a function like (at)EdMorton suggested there. (and using it, of course) – hek2mgl Apr 28 '15 at 19:31