1

I have some bash scripts I have been running on Ubuntu 14.04 and 16.04 for well over a year now. Some recent Ubuntu update has broken bash and I cannot figure out how to sort this out.

Example:

#!/bin/bash
INPUT=myinput.txt
OUTPUT=myoutput.txt
ACTION1="0;"

cat $INPUT | while read LINE
do
printf "\t\"~${LINE}\"\t\t$ACTION1\n" >> $OUTPUT
done

my script then loops through the file doing the printf statement as follows but this is the output produced

"~jetmo" 0;
"~spamme" 0;
"~baidu" 0;

example contents of myinput.txt

jetmon
spammen
baidu

Input lines containing a lowercase n character gets stripped out ?? Am I missing some major change in bash that occured, this started happening a week ago and is driving me insane now.

I have tried changing #!/bin/bash to #!/bin/sh with same results.

Also tried this method of looping through the input file and still get the same results.

#!/bin/bash
INPUT=myinput.txt
OUTPUT=myoutput.txt
ACTION1="0;"
while read LINE
do
printf "\t\"~${LINE}\"\t\t$ACTION1\n" >> $OUTPUT
done < $INPUT

Tried suggestion from comments using echo

echo -e -n "\t\"~${LINE}\"\t\t$ACTION1\n" >> $OUTPUT

and now I get this output

-e -n "~jetmo" 0;
-e -n "~spamme" 0;
-e -n "~baidu" 0;

Running hexdump -C myinput.txt gives this output

00000000  6a 65 74 6d 6f 6e 0a 73  70 61 6d 6d 65 6e 0a 62  |jetmon.spammen.b|
00000010  61 69 64 75 0a 0a                                 |aidu..|
00000016

Also changed all variable names to lowercase as suggested by Michael but still getting the same results.

#!/bin/bash
input=myinput.txt
output=myoutput.txt
action1="0;"

cat $input | while read line
do
printf "\t\"~${line}\"\t\t$action1\n" >> $output
done

THE SOLUTION The Mystery is Solved

thanks to everyone, but https://stackoverflow.com/users/96588/l0b0 nailed it on the head. I had an IFS=$'\n' hiding earlier on in my script.

I now have this final, better and safer formatting of printf thanks to the recommendations of Nahuel and Charles and it is working 100% ... cannot thank you all enough.

#!/bin/bash
input=myinput.txt
output=myoutput.txt
action1="0;"
while IFS= read -r LINE
do
printf '\t"~%s"\t\t%s\n' "${LINE}" "$ACTION1" >> "$output"
done < $input1
MitchellK
  • 2,322
  • 1
  • 16
  • 25
  • Try to check your input file for non-UNIX line endings. It works fine for me on Ubuntu 14.04. You can also replace `printf` with `echo -e -n`. – Michael Jul 04 '17 at 13:24
  • 2
    works fine for me on ubuntu 16.04... does this work? `printf '\t"~%s"\t\t%s\n' "${LINE}" "$ACTION1"`... also a few suggestions - use lowercase for variable names to avoid clash with env variables, see http://mywiki.wooledge.org/BashFAQ/001 for proper way to read file, and consider using text processing tools like `sed` instead of bash script – Sundeep Jul 04 '17 at 13:30
  • Thanks Michael and Sundeep. I tried using the echo -e -n and updated my original question with the results. I actually switched from using all lowercase variable names to uppercase when this started which started improving the problems as nothing was working at all. Even the echo strips off the "n" characters from the input. If I post my entire script section which later uses ed to do in place inserts into marker blocks can you help me get this sorted? I have been running these without issue on 16.04 for a year and on 14.04 too, even inside TravisCI which uses Trusty it does the same thing – MitchellK Jul 04 '17 at 13:42
  • Show the output of `hexdump -C myinput.txt` – Michael Jul 04 '17 at 13:47
  • Thanks again Michael, here is the output of that hexdump on this simple test file. ```00000000 6a 65 74 6d 6f 6e 0a 73 70 61 6d 6d 65 6e 0a 62 |jetmon.spammen.b| 00000010 61 69 64 75 0a 0a |aidu..| 00000016``` updated my original question with the hexdump output. – MitchellK Jul 04 '17 at 13:54
  • Which terminal are you using? Try to run your command in `xterm`. – Michael Jul 04 '17 at 13:56
  • @MichaelO., the POSIX spec for `echo` explicitly advises using `printf` instead. See APPLICATION USAGE and RATIONALE sections from http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html – Charles Duffy Jul 04 '17 at 14:04
  • It's already running in xterm `echo $TERM` gives me `xterm` ?? – MitchellK Jul 04 '17 at 14:05
  • `while IFS= read -r line` is going to be closer to byte-for-byte accuracy (and avoids inappropriate use of all-caps variable names outside their [POSIX-specified scope](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html) of variables with meaning to the OS or shell). – Charles Duffy Jul 04 '17 at 14:07
  • Charles, I went with printf when I developed these scripts over a year ago due to numerous recommendations and also having issues with echo. Funny thing is this has worked for a very long time day in and day out and now it's giving this weird behavior. Even happens inside the TravisCI environment which uses Trusty. – MitchellK Jul 04 '17 at 14:08
  • As @lobo suggests, `IFS='\n'` is the most likely culprit. (Can you actually reproduce the problem with **exactly** the script given in your question, no IFS assignments or otherwise)? – Charles Duffy Jul 04 '17 at 14:10
  • BTW, as another aside, `cat input | while ...; done` is both less efficient and breaks your variable scoping compared to `while ...; done – Charles Duffy Jul 04 '17 at 14:11
  • ...btw, amending your script to have a heredoc for the input would make it more self-contained, and thus closer to compliance with the [mcve] definition from the Help Center; right now it requires content not included for others to replicate the behavior you claim. – Charles Duffy Jul 04 '17 at 14:12

3 Answers3

3

This can happen if the internal field separator ($IFS) contains the letter n:

$ IFS=$' \t\nn' read line <<< foon
$ printf '%q\n' "$line"
foo

This is a fairly common mistake. Here is the correct value:

$ printf '%q\n' "$IFS"
$' \t\n'

Example:

$ IFS=$' \t\n' read line <<< foon
$ printf '%q\n' "$line"
foon
l0b0
  • 55,365
  • 30
  • 138
  • 223
  • That's true if your `n` is on the **end** of the line (with only one variable-name argument passed to `read`), but that doesn't appear to be the case in the OP's code -- unless I'm missing something? – Charles Duffy Jul 04 '17 at 14:08
  • Yeah, their input file has lines ending with `n`. – l0b0 Jul 04 '17 at 14:17
  • 1
    I could KICK myself (aarghhh) .... I had an `IFS='\n'` hiding in my script, oh my word thank you @l0b0 .... I really thought I was going mad. – MitchellK Jul 04 '17 at 14:19
1

It's safer to use %s to insert a string with printf for example if it can contain %

printf "\t\"~%s\"\t\t%s\n" "${LINE}" "$ACTION1" >> $OUTPUT

EDIT following comments, with single quotes in first argument because there is no variable expansion

printf '\t"~%s"\t\t%s\n' "${LINE}" "$ACTION1" >> "$OUTPUT"
Nahuel Fouilleul
  • 18,726
  • 2
  • 31
  • 36
  • I'd also suggest quoting `"$OUTPUT"` -- can get an "invalid redirection" error otherwise. – Charles Duffy Jul 04 '17 at 14:06
  • (And if you switched from double to single quotes for the printf string, you wouldn't need any backslash escaping within them). – Charles Duffy Jul 04 '17 at 14:14
  • Nahuel thanks for your input here too, can you explain why the `%s` is safer to use? – MitchellK Jul 04 '17 at 14:27
  • Charles please can you show me an example using my printf with single quotes ?? – MitchellK Jul 04 '17 at 14:28
  • @MitchellK %s is safer because first string argument is a format specification is not taken literally ; the characters `\ ` and `%` have special meaning – Nahuel Fouilleul Jul 04 '17 at 14:31
  • How would that then work with Charles' suggestion of using single quotes in my printf. I did try your print statement which works perfectly, just wondering why changing the printf statement to single quotes would also be better? – MitchellK Jul 04 '17 at 14:35
  • @MitchellK, `printf '\t"~%s"\t\t%s\n' "$LINE" "$ACTION1"` – Charles Duffy Jul 04 '17 at 14:35
  • @MitchellK, ...note that there, we don't need to put backslashes before the `"`s, and we don't need to think about what `\t` will evaluate to (whether it'll be replaced with a tab *before* being passed to `printf` vs after, for example). – Charles Duffy Jul 04 '17 at 14:36
  • Thank you Nahuel and Charles, works 100%. I have updated my original question with my final script format. – MitchellK Jul 04 '17 at 14:47
0

THE SOLUTION The Mystery is Solved

thanks to everyone, but https://stackoverflow.com/users/96588/l0b0 nailed it on the head. I had an IFS=$'\n' hiding earlier on in my script.

I now have this final, better and safer formatting of printf thanks to the recommendations of Nahuel and Charles and it is working 100% ... cannot thank you all enough.

#!/bin/bash
input=myinput.txt
output=myoutput.txt
action1="0;"
while IFS= read -r LINE
do
printf '\t"~%s"\t\t%s\n' "${LINE}" "$ACTION1" >> "$output"
done < $input1
MitchellK
  • 2,322
  • 1
  • 16
  • 25
  • Must really thank you all once again, I always pay my dues. The help from Charles, Lobo and Nahuel has taught me so much about printf, I have modified all my scripts to use this new formatting method and they are working flawlessly. – MitchellK Jul 05 '17 at 09:58