Printf two var's into a file - linux

I have a list of IP's I want to snmpget into a file. I'm having issues writing output to the file.
OID=1.3.6.1.2.1.25.3.2.1.3.1
cat printers.csv | while read IP ; do
OUT=$(snmpget -v1 -c public $IP $OID)
printf '%s, %s\n' $IP $OUT >> printerNames.csv
done
I'm new to the printf command. I'm guessing that's where it's messing up bc output is being written just incorrectly. Also, when there is no response it echos to console and I'd like it written to the output file. Any help will be much appreciated.

Try this:
OID=1.3.6.1.2.1.25.3.2.1.3.1
while read IP ; do
OUT=$(snmpget -v1 -c public "$IP" "$OID") && printf '%s, %s\n' "$IP" "$OUT"
done < printers.csv 2>&1 > printerNames.csv
It's a good idea to always quote parameter expansions unless you have a good reason not to. The redirections are applied to the while loop. read will read a line at a time from the input file (no cat needed); anything written to standard error is instead copied to standard output, and the standard output (errors included) is redirected to the output file. The printf is only executed if the snmpget command succeeds (I'm assuming it has a non-zero exit status if the lookup fails; that may not be the case).
It sounds like printers.csv has DOS line endings (\r\n). The carriage return is included as the last character of each line. When you print $IP, it prints the address, then the carriage return, which moves the cursor back to the beginning of the line. This causes the remainder of the line (, $OUT) to overwrite the address. To remove the carriage return, run the input file through dos2unix, or use some other method to turn the DOS line endings into UNIX line endings (\n alone).

Related

For loop in command line runs bash script reading from text file line by line

I have a bash script which asks for two arguments with a space between them. Now I would like to automate filling out the prompt in the command line with reading from a text file. The text file contains a list with the argument combinations.
So something like this in the command line I think;
for line in 'cat text.file' ; do script.sh ; done
Can this be done? What am I missing/doing wrong?
Thanks for the help.
A while loop is probably what you need. Put the space separated strings in the file text.file :
cat text.file
bingo yankee
bravo delta
Then write the script in question like below.
#!/bin/bash
while read -r arg1 arg2
do
/path/to/your/script.sh "$arg1" "$arg2"
done<text.file
Don't use for to read files line by line
Try something like this:
#!/bin/bash
ARGS=
while IFS= read -r line; do
ARGS="${ARGS} ${line}"
done < ./text.file
script.sh "$ARGS"
This would add each line to a variable which then is used as the arguments of your script.
'cat text.file' is a string literal, $(cat text.file) would expand to output of command however cat is useless because bash can read file using redirection, also with quotes it will be treated as a single argument and without it will split at space tab and newlines.
Bash syntax to read a file line by line, but will be slow for big files
while IFS= read -r line; do ... "$line"; done < text.file
unsetting IFS for read command preserves leading spaces
-r option preserves \
another way, to read whole file is content=$(<file), note the < inside the command substitution. so a creative way to read a file to array, each element a non-empty line:
read_to_array () {
local oldsetf=${-//[^f]} oldifs=$IFS
set -f
IFS=$'\n' array_content=($(<"$1")) IFS=$oldifs
[[ $oldsetf ]]||set +f
}
read_to_array "file"
for element in "${array_content[#]}"; do ...; done
oldsetf used to store current set -f or set +f setting
oldifs used to store current IFS
IFS=$'\n' to split on newlines (multiple newlines will be treated as one)
set -f avoid glob expansion for example in case line contains single *
note () around $() to store the result of splitting to an array
If I were to create a solution determined by the literal of what you ask for (using a for loop and parsing lines from a file) I would use iterations determined by the number of lines in the file (if it isn't too large).
Assuming each line has two strings separated by a single space (to be used as positional parameters in your script:
file="$1"
f_count="$(wc -l < $file)"
for line in $(seq 1 $f_count)
do
script.sh $(head -n $line $file | tail -n1) && wait
done
You may have a much better time using sjsam's solution however.

Variable still doesn't work on remote server

I have this part of script, which I cannot get to work. Been searching everywhere, but I must be missing something here.
export RULE=`cat iptables_sorted.txt`
ssh hostname << EOF
for line in $RULE; do
echo \$line >> test.txt (I have tried with and without slashes, etc...)
done
exit
EOF
After running this part of script, I get
stdin: is not a tty
-bash: line 2: syntax error near unexpected token `103.28.148.0/24'
-bash: line 2: `103.28.148.0/24'
...which is totally weird, because the iptables_sorted.txt is just full of ip ranges (when I run it locally, it works).
Newlines in $RULE cause the problem. Replace them by spaces:
RULE=$(< iptables_sorted.txt)
RULE=${RULE//$'\n'/ }
ssh hostname << EOF
for line in $RULE ; do
echo \$line >> test.txt
done
EOF
Note that this wouldn't work with lines containing whitespace.
Don't use for to iterate over a file; use while. This also demonstrates piping the output of the loop, not just every individual echo, to the remote host. cat is used to read the incoming data and redirect it to the final output file.
while IFS= read -r line; do
echo "$line"
done | ssh hostname 'cat > test.txt'

Print fifo's content in bash

I want to get a fifo's content and print it in a file, and I have this code:
path=$1 #path file get from script's input
if [ -p "$path" ];then #check if path is pipe
content = 'cat "$path"'
echo "$content" > output
exit 33
fi
My problem is that when I execute the cat "$path" line the script is stopped and the terminal displays the underscore.
I don't know how to solve this problem
P.S the fifo isn't empty and output is the file where I want to print fifo's content
If the FIFO is not empty, and there are no longer any file descriptors writing to that FIFO, you'll get EOF in the cat command. From man 7 pipe:
If all file descriptors referring to the write end of a pipe have been
closed, then an attempt to read(2) from the pipe will see end- of-file
(read(2) will return 0).
Source: man7.org/linux/man-pages/man7/pipe.7.html
Your assignment statement is incorrect.
Whitespace around = is not permitted.
You're confusing single quotes with backquotes. However, you should use $(...) for command substitution anyway.
The correct assignment is
content=$(cat "$path")
or more efficiently in bash,
content=$(< "$path")

Read line output in a shell script

I want to run a program (when executed it produces logdata) out of a shell script and write the output into a text file. I failed to do so :/
$prog is the executed prog -> socat /dev/ttyUSB0,b9600 STDOUT
$log/$FILE is just path to a .txt file
I had a Perl script to do this:
open (S,$prog) ||die "Cannot open $prog ($!)\n";
open (R,">>","$log") ||die "Cannot open logfile $log!\n";
while (<S>) {
my $date = localtime->strftime('%d.%m.%Y;%H:%M:%S;');
print "$date$_";
}
I tried to do this in a shell script like this
#!/bin/sh
FILE=/var/log/mylogfile.log
SOCAT=/usr/bin/socat
DEV=/dev/ttyUSB0
BAUD=,b9600
PROG=$SOCAT $DEV$BAUD STDOUT
exec 3<&0
exec 0<$PROG
while read -r line
do
DATE=`date +%d.%m.%Y;%H:%M:%S;`
echo $DATE$line >> $FILE
done
exec 0<&3
Doesn't work at all...
How do I read the output of that prog and pipe it into my text file using a shell script? What did I do wrong (if I didn't do everything wrong)?
Final code:
#!/bin/sh
FILE=/var/log/mylogfile.log
SOCAT=/usr/bin/socat
DEV=/dev/ttyUSB0
BAUD=,b9600
CMD="$SOCAT $DEV$BAUD STDOUT"
$CMD |
while read -r line
do
echo "$(date +'%d.%m.%Y;%H:%M:%S;')$line" >> $FILE
done
To read from a process, use process substitution
exec 0< <( $PROG )
/bin/sh doesn't support it, so use /bin/bash instead.
To assign several words to a variable, quote or backslash whitespace:
PROG="$SOCAT $DEV$BAUD STDOUT"
Semicolon is special in shell, quote it or backslash it:
DATE=$(date '+%d.%m.%Y;%H:%M:%S;')
Moreover, no exec's are needed:
while ...
...
done < <( $PROG )
You might even add > $FILE after done instead of adding each line separately to the file.
Original answer
You haven't shown the error messages — which would have been helpful.
Your problem, though, is probably this line:
DATE=`date +%d.%m.%Y;%H:%M:%S;`
where the semicolons mark the end of a command, and there likely isn't a command %H that does anything useful, etc.
You need quotes around the format argument to date, and I'd use single quotes for this job:
DATE=$(date +'%d.%m.%Y;%H:%M:%S;')
or even replace the two lines in the body of the loop with:
echo "$(date +'%d.%m.%Y;%H:%M:%S;')$line" >> $FILE
The double quotes prevent a variety of problems.
That assumes you fix a bunch of other problems, such as the setting of the variables FILE and prog. Also, I'd probably use:
exec > $FILE
to initially zap the output file and then all subsequent standard output would go to that file, so the echo line becomes:
echo "$(date +'%d.%m.%Y;%H:%M:%S;')$line"
Amended answer
The question was originally missing lots of key information. It eventually got updated to include the complete code.
The problem I identified originally remains an issue, but you weren't running into it because the input redirection was not working. If you want the input to come from a process, use a pipe, or possibly process substitution. However, note that you have #!/bin/sh as your shebang line, and /bin/sh won't recognized process substitution; either change the shebang or use the pipe notation. Note that process substitution has advantages if the loop is setting variables that need to be accessed after the loop is complete.
$SOCAT $DEV$BAUD STDOUT |
while read -r line
do
…
done
or
while read -r line
do
…
done < <($SOCAT $DEV$BAUD STDOUT)
Note that your code contains the line:
PROG=$SOCAT $DEV$BAUD STDOUT
This runs the command identified by $DEV$BAUD with the argument STDOUT and the environment variable PROG set to the value of $SOCAT. That is not what you wanted.
You could use an array:
PROG=($SOCAT $DEV$BAUD STDOUT)
and then run:
"${PROG[#]}"
either in the pipe line:
"${PROG[#]}" |
while read -r line
do
…
done
or with process substitution:
while read -r line
do
…
done < <("${PROG[#]}")
Note that unless there is code after the final exec 0<&3, there was no particular virtue in the redirections involving file descriptor 3. You should also close 3 when you're done with it:
exec 0<&3 3>&-
The 'final' code includes the lines:
CMD="$SOCAT $DEV$BAUD STDOUT"
$CMD |
while read -r line
This works OK because there are no spaces in the arguments to the command. That's a common case, but beware of spaces in arguments and file paths.

Bash: Variable substitution in quoted string looks like something from the Twilight Zone

I've got a bash script that's reading essentially the output of telnet (actually socat to a unix domain socket).
while read -r LINE; do
if [ "$USER_DATA_FLAG" == "true" ]; then #Evaluates to false for the moment
...
else
printf "%s\n" "Variable> $LINE" >>$DEBUG_LOG
printf "%s\n" "Line xxxxz $LINE yxxxxx" >>$DEBUG_LOG
...
fi
done
This yields the following incomprehensible output:
Variable> >INFO:OpenVPN Management Interface Version 1 -- type 'help' for more info
yxxxxxxxxz >INFO:OpenVPN Management Interface Version 1 -- type 'help' for more info
Variable> OpenVPN CLIENT LIST
yxxxxxxxxz OpenVPN CLIENT LIST
The first printf statement works fine and shows exactly what I expect based on my experience at the terminal. But the second printf statement goes nuts, reverses the order of some the characters and prints $LINE entirely in the wrong place!
$LINE has a \r in it, which is sending the cursor back to column 1. Use parameter substitution or tr to remove it.
$ foo=$'1\r23'
$ echo "$foo"
23
$ echo "${foo/$'\r'/}"
123
This will ramble on a bit. The first thing I noticed was this:
printf "%s\n" "Variable> $LINE" >>$DEBUG_LOG
And I immediately think, "this is C code." In shell programming, printf is somewhat an odd tool which is sometimes useful, unlike the printf in C, which is the first thing you reach for when your printing something out. Try this instead:
echo "Variable> $LINE" >>$DEBUG_LOG
The other problem is potentially with telnet. If you parse telnet output in a shell, you're going to get some characters you don't want -- carriage returns, for example. The default value of IFS for bash is <space><tab><newline>, which will NOT catch carriage returns and the carriage returns WILL mess with the location of characters in your terminal.
Ideally, you would get a proper telnet client and use it non-interactively. However, in a pinch, you can do this:
while tr -d '\r' | read -r LINE; do
...
This will strip out carriage returns -- but there could still be other telnet junk in there, including WILL/WONT/DO/DONT commands, and NUL bytes.

Resources