1

I have the following snippet of code where I intend to:

Iterate 5 times the loop;
get a line from a file (each iteration I need the next line fetched);
use the line as a title for a file;
put a timestamp in the beginning of it;
use the line as argument for ping; print some sentences informing the user about the progress of the code;

The iteration already works.
If I skip the sed part, everything works, but I run the ping 5 times against the same target.
Each line of the file hosts.txt has a single IP address which I intend to ping.

for ((i>=1;i<=5;i++))  
do  
sed -n "$i p" hosts.txt | read output  
    touch "$output.txt"  
    date >> "$output.txt"  
    printf "\nComeçando o teste de $output."  
    printf "\nTeste em andamento."  
    ping -c 10 -i 1 "$output" >> "$output.txt"  
done  

I believe the problem is that I'm doing something wrong about sed syntax. If I try on bash directly sed -n 1p hosts, I get the first line. But as I need that number to be $i, if I put $i right before p bash interprets ip as a variable instead of variable i plus argument p after it.

How can I correct this behaviour and not mess this thing again?

  • That seems a little bit advanced for me, as I didn't understood half you said. You lost me ath the awk part. :P I'm still a newbie, one day I will get there (knowing awk and how to avoid stupid inefficiences). If you may have any tips for me, I would appreciate, as I'm still learning! :D – Rafael Umbelino May 22 '16 at 22:31
  • @don_crissti The fact sed | read is inefficient is anecdotal compared to the fact it doesn't work in the first place the way it is used here. – jlliagre May 22 '16 at 22:36
  • the loop initialization wasn't doing what you wanted it to do: i>=1 is a test, not an assignment – Jeff Schaller May 22 '16 at 22:56
  • The variable i was initialized outside of the loop. I posted only the loop. – Rafael Umbelino May 22 '16 at 23:06
  • A simpler version: i=5; for h in $(head -n "$i" hosts.txt) ; do echo "Testing $h" ; date > "$h.txt" ; ping -c 10 -i 1 "$h" >> "$h.txt" ; done. Like your version, this does no checking of the input lines, assumes hosts.txt is formatted as exactly one hostname/ip per line...so can break on any lines where that isn't true. – cas May 23 '16 at 1:56
3

I'm assuming you are using bash so this should work better:

for ((i=1;i<=5;i++))  
do  
    sed -n "$i p" hosts.txt | (
        read output  
        touch "$output.txt"  
        date >> "$output.txt"  
        printf "\nComeçando o teste de $output."  
        printf "\nTeste em andamento."  
        ping -c 10 -i 1 "$output" >> "$output.txt"  
    )
done 

The issue you were suffering is bash, like most shell interpreters outside ksh put all pipeline components in a subshell so the output variable is immediately lost after being set.

Note also that I corrected your for loop which as written had an undefined behavior because the i variable was not initialized.

  • May I say you're a savior among the earth? :) Why the heck the lack of () was screwing with my code, sire? – Rafael Umbelino May 22 '16 at 20:55
  • Answer updated with the explanation. – jlliagre May 22 '16 at 21:37
  • Thank you for your explanation sire! I saw the correction on the for loop, but I had already initialized the variable i before the loop. Which would be the best practice? Initialize it before the loop or directly on it? – Rafael Umbelino May 22 '16 at 21:50
  • Doing it inside the loop is the usual and best practice. *however, if the loop variable is already initialized for some reason and you want to use its value, you can leave the first expression empty: for ((;i<=5;i++) – jlliagre May 22 '16 at 21:59
  • Thank you sire, helped me a bit more than I expected. :) – Rafael Umbelino May 22 '16 at 22:01
2

The problem is, as you have observes, both i and ip are valid variable names. So if you want to use the value $i immediately followed by the character p, you can use bendy braces to clearly delimit where the variable name starts and ends:

sed -n "${i}p" hosts.txt | read output
  • Hey there! I've tried that and simply got a no-go. Got an error messagem from bash when I tried to run my code. Putting ( ) as suggested on another comment solved the problem. – Rafael Umbelino May 22 '16 at 21:13
  • Unable to replicate your no-go situation. Tested with ip=3; sed -n "${i}p" /path/to/file printing only line three of the file. – DopeGhoti May 22 '16 at 21:17
  • Tried again and got an error message: "ping: unknown host intermitencia.sh: line 16: ${$i} p: substituição incorreta". But following the tip on the comment about putting ( ) solved the problem I was having. – Rafael Umbelino May 22 '16 at 21:22
  • 2
    Sed p instruction doesn't need to immediately follow the line number so the curly quotes aren't necessary here, a space is sufficient to avoid any class. – jlliagre May 22 '16 at 21:41
  • 1
    @RafaelUmbelino: Did you type ${$i} (as indicated by the error message you quote)?  Take another look at DopeGhoti's suggestion — you may have typed an extra $. – G-Man May 23 '16 at 7:41
1

A slightly simpler solution is

for ((i=1; i<=5; i++))
do
    read output
    date >> "$output.txt"
    printf "\nComeçando o teste de %s." "$output"
    printf "\nTeste em andamento."
    ping -c 10 -i 1 "$output" >> "$output.txt"
done < hosts.txt

Note the < hosts.txt at the end of the last line, after the done.  This opens hosts.txt once and just reads the first five lines from it.  As mentioned by don_crissti, doing sed … hosts.txt | read output in the loop means that you are reading the hosts.txt file five times.  Since the read output is parallel with the rest of the loop (not in a pipeline), the rest of the code in the loop will have access to the value of output read from the file.  Also (as don also pointed out), you don’t need touch; command>>file will create file if it doesn’t already exist.

Note that if one of your hostname.txt files already exists, this code will append the new information to it.  If you want to discard old data and start from scratch, do date > "$output.txt" instead of date >> "$output.txt".

Be careful of passing unknown data directly to printf.  In the (unlikely?) event that one of your “hostnames” has a % in it, your code will garble that name.  It’s better to pass foreign data to %s (to print a string).

You don’t say whether you want to read only the first five lines of the hosts.txt file and then stop, or whether you want to read the entire file, and you happen to know that it is five lines long.  If you want to read the entire file, just do

while read output
do
    date >> "$output.txt"
    printf "\nComeçando o teste de %s." "$output"
    printf "\nTeste em andamento."
    ping -c 10 -i 1 "$output" >> "$output.txt"
done < hosts.txt

When you get to the end of the file, the read output statement will fail, and the loop will terminate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.