What am I doing wrong trying to write a bash script that returns the number of the next available port?
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}
I'm trying to write a script that takes a port number as an argument. It returns the next port that is not assigned to anything, and checks it by using the file /etc/services. If the port is taken (i.e. listed in the document), it adds one and then tries again. I can't seem to get this script to return anything for "found" - it always equals 0 so I never enter the while loop. What am I doing wrong?
#!/bin/bash
port=$1
found=$(cat /etc/services | grep -o '[[:space:]]$port/' | wc -l)
while [ $found -ge 1 ]; do
$port=$($port+1)
#done
echo "found: $found"
echo "port: $port"
(ignore the comment in front of done, that's not the problem)
bash shell-script shell networking grep
add a comment |
I'm trying to write a script that takes a port number as an argument. It returns the next port that is not assigned to anything, and checks it by using the file /etc/services. If the port is taken (i.e. listed in the document), it adds one and then tries again. I can't seem to get this script to return anything for "found" - it always equals 0 so I never enter the while loop. What am I doing wrong?
#!/bin/bash
port=$1
found=$(cat /etc/services | grep -o '[[:space:]]$port/' | wc -l)
while [ $found -ge 1 ]; do
$port=$($port+1)
#done
echo "found: $found"
echo "port: $port"
(ignore the comment in front of done, that's not the problem)
bash shell-script shell networking grep
You're not reinitializing found inside the loop, and you can't assign $found; you want found= ...
– Jeff Schaller♦
May 4 '17 at 0:22
2
Plug for shellcheck.net for the syntactic error checking
– Jeff Schaller♦
May 4 '17 at 0:24
I have transcribed your image to text. For a bit of rationale behind this, see this Meta post
– Fox
May 4 '17 at 1:49
1
Related: unix.stackexchange.com/questions/180492/…
– Kusalananda♦
May 4 '17 at 6:38
I wonder if you meant for thegrepcheck to be done within the loop, now you'd just return the next number even if that was reserved too.
– ilkkachu
May 4 '17 at 16:58
add a comment |
I'm trying to write a script that takes a port number as an argument. It returns the next port that is not assigned to anything, and checks it by using the file /etc/services. If the port is taken (i.e. listed in the document), it adds one and then tries again. I can't seem to get this script to return anything for "found" - it always equals 0 so I never enter the while loop. What am I doing wrong?
#!/bin/bash
port=$1
found=$(cat /etc/services | grep -o '[[:space:]]$port/' | wc -l)
while [ $found -ge 1 ]; do
$port=$($port+1)
#done
echo "found: $found"
echo "port: $port"
(ignore the comment in front of done, that's not the problem)
bash shell-script shell networking grep
I'm trying to write a script that takes a port number as an argument. It returns the next port that is not assigned to anything, and checks it by using the file /etc/services. If the port is taken (i.e. listed in the document), it adds one and then tries again. I can't seem to get this script to return anything for "found" - it always equals 0 so I never enter the while loop. What am I doing wrong?
#!/bin/bash
port=$1
found=$(cat /etc/services | grep -o '[[:space:]]$port/' | wc -l)
while [ $found -ge 1 ]; do
$port=$($port+1)
#done
echo "found: $found"
echo "port: $port"
(ignore the comment in front of done, that's not the problem)
bash shell-script shell networking grep
bash shell-script shell networking grep
edited May 20 '17 at 10:56
Jeff Schaller♦
44.7k1162145
44.7k1162145
asked May 3 '17 at 22:22
user229747
You're not reinitializing found inside the loop, and you can't assign $found; you want found= ...
– Jeff Schaller♦
May 4 '17 at 0:22
2
Plug for shellcheck.net for the syntactic error checking
– Jeff Schaller♦
May 4 '17 at 0:24
I have transcribed your image to text. For a bit of rationale behind this, see this Meta post
– Fox
May 4 '17 at 1:49
1
Related: unix.stackexchange.com/questions/180492/…
– Kusalananda♦
May 4 '17 at 6:38
I wonder if you meant for thegrepcheck to be done within the loop, now you'd just return the next number even if that was reserved too.
– ilkkachu
May 4 '17 at 16:58
add a comment |
You're not reinitializing found inside the loop, and you can't assign $found; you want found= ...
– Jeff Schaller♦
May 4 '17 at 0:22
2
Plug for shellcheck.net for the syntactic error checking
– Jeff Schaller♦
May 4 '17 at 0:24
I have transcribed your image to text. For a bit of rationale behind this, see this Meta post
– Fox
May 4 '17 at 1:49
1
Related: unix.stackexchange.com/questions/180492/…
– Kusalananda♦
May 4 '17 at 6:38
I wonder if you meant for thegrepcheck to be done within the loop, now you'd just return the next number even if that was reserved too.
– ilkkachu
May 4 '17 at 16:58
You're not reinitializing found inside the loop, and you can't assign $found; you want found= ...
– Jeff Schaller♦
May 4 '17 at 0:22
You're not reinitializing found inside the loop, and you can't assign $found; you want found= ...
– Jeff Schaller♦
May 4 '17 at 0:22
2
2
Plug for shellcheck.net for the syntactic error checking
– Jeff Schaller♦
May 4 '17 at 0:24
Plug for shellcheck.net for the syntactic error checking
– Jeff Schaller♦
May 4 '17 at 0:24
I have transcribed your image to text. For a bit of rationale behind this, see this Meta post
– Fox
May 4 '17 at 1:49
I have transcribed your image to text. For a bit of rationale behind this, see this Meta post
– Fox
May 4 '17 at 1:49
1
1
Related: unix.stackexchange.com/questions/180492/…
– Kusalananda♦
May 4 '17 at 6:38
Related: unix.stackexchange.com/questions/180492/…
– Kusalananda♦
May 4 '17 at 6:38
I wonder if you meant for the
grep check to be done within the loop, now you'd just return the next number even if that was reserved too.– ilkkachu
May 4 '17 at 16:58
I wonder if you meant for the
grep check to be done within the loop, now you'd just return the next number even if that was reserved too.– ilkkachu
May 4 '17 at 16:58
add a comment |
2 Answers
2
active
oldest
votes
Overview
There are just a few minor errors with this script, and a few stylistic things I would change. Let's go through the original line-by-line:
#!/bin/bash
port=$1
A typical #! line and a simple assignment, we're off to a good start.
found=$(cat /etc/services | grep -o '[[:space:]]$port/' | wc -l)
This line has a small issue that one may not notice at first glance. Since $port is in strong quotes, '…$port', it won't expand to its value. You'll want to use weak quotes instead, "…$port". Also, you don't really need cat here. grep takes a filename as an argument, but if it didn't you could always use redirection (grep 'pattern' < /etc/services). Further, grep -c writes a count of matching lines, so wc -l is redundant here as well.
while [ $found -ge 1 ]; do
$port=$($port+1)
done
The loop seems almost fine. You'll want to quote the entities in the test in case $found could possibly be empty. Also, you assign with port=, not $port= (but you've used the correct form above, so I assume this was just a typo). Finally $($port+1) means (if port is 22, for example) "The output of the command 22+1". Clearly you want "The result of the arithmetic expression 22+1", which is the very similar $(($port+1)).
echo "found: $found"
echo "port: $port"
These lines are alright as is.
Considering the logic
Now, if you make all of the above changes so that the program is syntactically correct, it still will not do what you want. You don't list a "correct" output, so I'm assuming you want:
found: <the number of occurrences of $1 in /etc/services>
port: <the next free port>
If these assumptions are incorrect, let me know in the comments and I will edit accordingly.
The script as is will never return if the given port does appear in /etc/services because you never update found. So you may think it would be wise to copy the found=… line into the loop, but that is not so! If you did that, then the script would always return found: 0. I think the best thing to do here is to create a function:
found () {
grep -co "[[:space:]]$1/" /etc/services
}
but now the exit value of this function can directly work as a condition. You'll want to quiet the output so it doesn't appear along with yours, and then you'll no longer need the -co flags, but you will want -q:
while found "$port"; do
Then, to return the number of occurrences of the input port, it would be
echo "found: $(found "$1")"
or you could use printf.
Putting it all together
I think this is the script you were trying to write:
#!/bin/sh
found () {
grep -q "[[:space:]]$1/" /etc/services
}
port=$1
while found "$port"; do
port=$(($port+1))
done
printf 'found: %dnport %dn' "$(found "$1")" "$port"
Bonus
As I was writing this, my initial reaction was that counting lines matching a given pattern could be done in awk as well as grep. It then occurred to me that the entire program could be written in awk. As a bonus, here is one implementation of that (though here I assume that /etc/services is sorted by port number):
#!/usr/bin/awk -f
BEGIN {
FS = "[ t/][ t/]*"
ARGC = 2
PORT = ARGV[1]
OPORT = PORT
ARGV[1] = "/etc/services"
}
($2 == OPORT) {
FOUND = FOUND + 1
}
(NEXT == 0 && NR > 1 && $2 > PORT) {
NEXT = ($2 > PORT + 1) ? PORT + 1 : NEXT
PORT = $2
}
END {
printf "found: %dnport: %dn", FOUND, (FOUND == 0) ? OPORT : NEXT
}
I got that much. On my system, if you run this with a port number of 22, it will sayfound: 3, port: 24, as in "There were 3 entries for port 22 in /etc/services, and the next available port is 24". Are you looking for something different?
– Fox
May 4 '17 at 3:20
@Kay I originally posted an incorrectawkversion. Then I removed it, but decided to fix it and put it back in. You might try both thebashandawkversions. You'll find that theawkversion is noticeably faster, because it does not need to search through the entire file for each increment ofPORT
– Fox
May 4 '17 at 3:37
I have to object to'[[:space:]]'"$1"'/'on aesthetic grounds. The/aren't special in double quotes, so you could just do"[[:space:]]$1/"and avoid the multi-quote dance...
– ilkkachu
May 4 '17 at 17:00
@ilkkachu After thinking for a while, I agree with you. Edited to universal weak quotes. I was already in violation of my own preference for strong quotes whenever possible with"1"
– Fox
May 5 '17 at 0:05
This would beea lot more idiomatic if the function's exit code was 0 for found, non-zero otherwise. Capturing the output number and comparing it lexically to another is clumsy, inelegant, and error-prone, and a common antipattern.
– tripleee
May 5 '17 at 14:03
add a comment |
Minus the math part, here's a quick and dirty POC to accomplish what you're going for, but using an "if" statement in lieu of "while" loop conditions for your logic.
#!/bin/bash
port=$1
echo $port | while read a; do
found=$(cat /etc/services | grep -Eo '[0-9]{1,5}/' | sed 's/^/ /' | grep ' '$a'/')
if [[ $found ]];
then echo port $a is being used
else echo port $a is not being used
fi
done
Edit: The issue was stemming from the $found variable. the single quotes in the grep make $port get interpreted literally, so its grepping for the string
' $port/' instead of ' 80/' for example.
fix: just change the single quotes to double quotes.
found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)
I haven't read this thoroughly, but "grepping for a space in front of the port numbers" is not what[[:space:]]means. That is a POSIX character class that matches any whitespace character, including tab, formfeed, or U2008 (thin space)
– Fox
May 4 '17 at 16:19
@Fox you are right about the [[:space:]], I did not know that. In that case all you need to do is change your single quotes to double quotes to allow string interpolation. The single quotes interpret '$port' as a literal instead of substituting the value, which should be a number in this case.found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)
– TheWhiteCong
May 4 '17 at 16:41
add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "106"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f362921%2fwhat-am-i-doing-wrong-trying-to-write-a-bash-script-that-returns-the-number-of-t%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
Overview
There are just a few minor errors with this script, and a few stylistic things I would change. Let's go through the original line-by-line:
#!/bin/bash
port=$1
A typical #! line and a simple assignment, we're off to a good start.
found=$(cat /etc/services | grep -o '[[:space:]]$port/' | wc -l)
This line has a small issue that one may not notice at first glance. Since $port is in strong quotes, '…$port', it won't expand to its value. You'll want to use weak quotes instead, "…$port". Also, you don't really need cat here. grep takes a filename as an argument, but if it didn't you could always use redirection (grep 'pattern' < /etc/services). Further, grep -c writes a count of matching lines, so wc -l is redundant here as well.
while [ $found -ge 1 ]; do
$port=$($port+1)
done
The loop seems almost fine. You'll want to quote the entities in the test in case $found could possibly be empty. Also, you assign with port=, not $port= (but you've used the correct form above, so I assume this was just a typo). Finally $($port+1) means (if port is 22, for example) "The output of the command 22+1". Clearly you want "The result of the arithmetic expression 22+1", which is the very similar $(($port+1)).
echo "found: $found"
echo "port: $port"
These lines are alright as is.
Considering the logic
Now, if you make all of the above changes so that the program is syntactically correct, it still will not do what you want. You don't list a "correct" output, so I'm assuming you want:
found: <the number of occurrences of $1 in /etc/services>
port: <the next free port>
If these assumptions are incorrect, let me know in the comments and I will edit accordingly.
The script as is will never return if the given port does appear in /etc/services because you never update found. So you may think it would be wise to copy the found=… line into the loop, but that is not so! If you did that, then the script would always return found: 0. I think the best thing to do here is to create a function:
found () {
grep -co "[[:space:]]$1/" /etc/services
}
but now the exit value of this function can directly work as a condition. You'll want to quiet the output so it doesn't appear along with yours, and then you'll no longer need the -co flags, but you will want -q:
while found "$port"; do
Then, to return the number of occurrences of the input port, it would be
echo "found: $(found "$1")"
or you could use printf.
Putting it all together
I think this is the script you were trying to write:
#!/bin/sh
found () {
grep -q "[[:space:]]$1/" /etc/services
}
port=$1
while found "$port"; do
port=$(($port+1))
done
printf 'found: %dnport %dn' "$(found "$1")" "$port"
Bonus
As I was writing this, my initial reaction was that counting lines matching a given pattern could be done in awk as well as grep. It then occurred to me that the entire program could be written in awk. As a bonus, here is one implementation of that (though here I assume that /etc/services is sorted by port number):
#!/usr/bin/awk -f
BEGIN {
FS = "[ t/][ t/]*"
ARGC = 2
PORT = ARGV[1]
OPORT = PORT
ARGV[1] = "/etc/services"
}
($2 == OPORT) {
FOUND = FOUND + 1
}
(NEXT == 0 && NR > 1 && $2 > PORT) {
NEXT = ($2 > PORT + 1) ? PORT + 1 : NEXT
PORT = $2
}
END {
printf "found: %dnport: %dn", FOUND, (FOUND == 0) ? OPORT : NEXT
}
I got that much. On my system, if you run this with a port number of 22, it will sayfound: 3, port: 24, as in "There were 3 entries for port 22 in /etc/services, and the next available port is 24". Are you looking for something different?
– Fox
May 4 '17 at 3:20
@Kay I originally posted an incorrectawkversion. Then I removed it, but decided to fix it and put it back in. You might try both thebashandawkversions. You'll find that theawkversion is noticeably faster, because it does not need to search through the entire file for each increment ofPORT
– Fox
May 4 '17 at 3:37
I have to object to'[[:space:]]'"$1"'/'on aesthetic grounds. The/aren't special in double quotes, so you could just do"[[:space:]]$1/"and avoid the multi-quote dance...
– ilkkachu
May 4 '17 at 17:00
@ilkkachu After thinking for a while, I agree with you. Edited to universal weak quotes. I was already in violation of my own preference for strong quotes whenever possible with"1"
– Fox
May 5 '17 at 0:05
This would beea lot more idiomatic if the function's exit code was 0 for found, non-zero otherwise. Capturing the output number and comparing it lexically to another is clumsy, inelegant, and error-prone, and a common antipattern.
– tripleee
May 5 '17 at 14:03
add a comment |
Overview
There are just a few minor errors with this script, and a few stylistic things I would change. Let's go through the original line-by-line:
#!/bin/bash
port=$1
A typical #! line and a simple assignment, we're off to a good start.
found=$(cat /etc/services | grep -o '[[:space:]]$port/' | wc -l)
This line has a small issue that one may not notice at first glance. Since $port is in strong quotes, '…$port', it won't expand to its value. You'll want to use weak quotes instead, "…$port". Also, you don't really need cat here. grep takes a filename as an argument, but if it didn't you could always use redirection (grep 'pattern' < /etc/services). Further, grep -c writes a count of matching lines, so wc -l is redundant here as well.
while [ $found -ge 1 ]; do
$port=$($port+1)
done
The loop seems almost fine. You'll want to quote the entities in the test in case $found could possibly be empty. Also, you assign with port=, not $port= (but you've used the correct form above, so I assume this was just a typo). Finally $($port+1) means (if port is 22, for example) "The output of the command 22+1". Clearly you want "The result of the arithmetic expression 22+1", which is the very similar $(($port+1)).
echo "found: $found"
echo "port: $port"
These lines are alright as is.
Considering the logic
Now, if you make all of the above changes so that the program is syntactically correct, it still will not do what you want. You don't list a "correct" output, so I'm assuming you want:
found: <the number of occurrences of $1 in /etc/services>
port: <the next free port>
If these assumptions are incorrect, let me know in the comments and I will edit accordingly.
The script as is will never return if the given port does appear in /etc/services because you never update found. So you may think it would be wise to copy the found=… line into the loop, but that is not so! If you did that, then the script would always return found: 0. I think the best thing to do here is to create a function:
found () {
grep -co "[[:space:]]$1/" /etc/services
}
but now the exit value of this function can directly work as a condition. You'll want to quiet the output so it doesn't appear along with yours, and then you'll no longer need the -co flags, but you will want -q:
while found "$port"; do
Then, to return the number of occurrences of the input port, it would be
echo "found: $(found "$1")"
or you could use printf.
Putting it all together
I think this is the script you were trying to write:
#!/bin/sh
found () {
grep -q "[[:space:]]$1/" /etc/services
}
port=$1
while found "$port"; do
port=$(($port+1))
done
printf 'found: %dnport %dn' "$(found "$1")" "$port"
Bonus
As I was writing this, my initial reaction was that counting lines matching a given pattern could be done in awk as well as grep. It then occurred to me that the entire program could be written in awk. As a bonus, here is one implementation of that (though here I assume that /etc/services is sorted by port number):
#!/usr/bin/awk -f
BEGIN {
FS = "[ t/][ t/]*"
ARGC = 2
PORT = ARGV[1]
OPORT = PORT
ARGV[1] = "/etc/services"
}
($2 == OPORT) {
FOUND = FOUND + 1
}
(NEXT == 0 && NR > 1 && $2 > PORT) {
NEXT = ($2 > PORT + 1) ? PORT + 1 : NEXT
PORT = $2
}
END {
printf "found: %dnport: %dn", FOUND, (FOUND == 0) ? OPORT : NEXT
}
I got that much. On my system, if you run this with a port number of 22, it will sayfound: 3, port: 24, as in "There were 3 entries for port 22 in /etc/services, and the next available port is 24". Are you looking for something different?
– Fox
May 4 '17 at 3:20
@Kay I originally posted an incorrectawkversion. Then I removed it, but decided to fix it and put it back in. You might try both thebashandawkversions. You'll find that theawkversion is noticeably faster, because it does not need to search through the entire file for each increment ofPORT
– Fox
May 4 '17 at 3:37
I have to object to'[[:space:]]'"$1"'/'on aesthetic grounds. The/aren't special in double quotes, so you could just do"[[:space:]]$1/"and avoid the multi-quote dance...
– ilkkachu
May 4 '17 at 17:00
@ilkkachu After thinking for a while, I agree with you. Edited to universal weak quotes. I was already in violation of my own preference for strong quotes whenever possible with"1"
– Fox
May 5 '17 at 0:05
This would beea lot more idiomatic if the function's exit code was 0 for found, non-zero otherwise. Capturing the output number and comparing it lexically to another is clumsy, inelegant, and error-prone, and a common antipattern.
– tripleee
May 5 '17 at 14:03
add a comment |
Overview
There are just a few minor errors with this script, and a few stylistic things I would change. Let's go through the original line-by-line:
#!/bin/bash
port=$1
A typical #! line and a simple assignment, we're off to a good start.
found=$(cat /etc/services | grep -o '[[:space:]]$port/' | wc -l)
This line has a small issue that one may not notice at first glance. Since $port is in strong quotes, '…$port', it won't expand to its value. You'll want to use weak quotes instead, "…$port". Also, you don't really need cat here. grep takes a filename as an argument, but if it didn't you could always use redirection (grep 'pattern' < /etc/services). Further, grep -c writes a count of matching lines, so wc -l is redundant here as well.
while [ $found -ge 1 ]; do
$port=$($port+1)
done
The loop seems almost fine. You'll want to quote the entities in the test in case $found could possibly be empty. Also, you assign with port=, not $port= (but you've used the correct form above, so I assume this was just a typo). Finally $($port+1) means (if port is 22, for example) "The output of the command 22+1". Clearly you want "The result of the arithmetic expression 22+1", which is the very similar $(($port+1)).
echo "found: $found"
echo "port: $port"
These lines are alright as is.
Considering the logic
Now, if you make all of the above changes so that the program is syntactically correct, it still will not do what you want. You don't list a "correct" output, so I'm assuming you want:
found: <the number of occurrences of $1 in /etc/services>
port: <the next free port>
If these assumptions are incorrect, let me know in the comments and I will edit accordingly.
The script as is will never return if the given port does appear in /etc/services because you never update found. So you may think it would be wise to copy the found=… line into the loop, but that is not so! If you did that, then the script would always return found: 0. I think the best thing to do here is to create a function:
found () {
grep -co "[[:space:]]$1/" /etc/services
}
but now the exit value of this function can directly work as a condition. You'll want to quiet the output so it doesn't appear along with yours, and then you'll no longer need the -co flags, but you will want -q:
while found "$port"; do
Then, to return the number of occurrences of the input port, it would be
echo "found: $(found "$1")"
or you could use printf.
Putting it all together
I think this is the script you were trying to write:
#!/bin/sh
found () {
grep -q "[[:space:]]$1/" /etc/services
}
port=$1
while found "$port"; do
port=$(($port+1))
done
printf 'found: %dnport %dn' "$(found "$1")" "$port"
Bonus
As I was writing this, my initial reaction was that counting lines matching a given pattern could be done in awk as well as grep. It then occurred to me that the entire program could be written in awk. As a bonus, here is one implementation of that (though here I assume that /etc/services is sorted by port number):
#!/usr/bin/awk -f
BEGIN {
FS = "[ t/][ t/]*"
ARGC = 2
PORT = ARGV[1]
OPORT = PORT
ARGV[1] = "/etc/services"
}
($2 == OPORT) {
FOUND = FOUND + 1
}
(NEXT == 0 && NR > 1 && $2 > PORT) {
NEXT = ($2 > PORT + 1) ? PORT + 1 : NEXT
PORT = $2
}
END {
printf "found: %dnport: %dn", FOUND, (FOUND == 0) ? OPORT : NEXT
}
Overview
There are just a few minor errors with this script, and a few stylistic things I would change. Let's go through the original line-by-line:
#!/bin/bash
port=$1
A typical #! line and a simple assignment, we're off to a good start.
found=$(cat /etc/services | grep -o '[[:space:]]$port/' | wc -l)
This line has a small issue that one may not notice at first glance. Since $port is in strong quotes, '…$port', it won't expand to its value. You'll want to use weak quotes instead, "…$port". Also, you don't really need cat here. grep takes a filename as an argument, but if it didn't you could always use redirection (grep 'pattern' < /etc/services). Further, grep -c writes a count of matching lines, so wc -l is redundant here as well.
while [ $found -ge 1 ]; do
$port=$($port+1)
done
The loop seems almost fine. You'll want to quote the entities in the test in case $found could possibly be empty. Also, you assign with port=, not $port= (but you've used the correct form above, so I assume this was just a typo). Finally $($port+1) means (if port is 22, for example) "The output of the command 22+1". Clearly you want "The result of the arithmetic expression 22+1", which is the very similar $(($port+1)).
echo "found: $found"
echo "port: $port"
These lines are alright as is.
Considering the logic
Now, if you make all of the above changes so that the program is syntactically correct, it still will not do what you want. You don't list a "correct" output, so I'm assuming you want:
found: <the number of occurrences of $1 in /etc/services>
port: <the next free port>
If these assumptions are incorrect, let me know in the comments and I will edit accordingly.
The script as is will never return if the given port does appear in /etc/services because you never update found. So you may think it would be wise to copy the found=… line into the loop, but that is not so! If you did that, then the script would always return found: 0. I think the best thing to do here is to create a function:
found () {
grep -co "[[:space:]]$1/" /etc/services
}
but now the exit value of this function can directly work as a condition. You'll want to quiet the output so it doesn't appear along with yours, and then you'll no longer need the -co flags, but you will want -q:
while found "$port"; do
Then, to return the number of occurrences of the input port, it would be
echo "found: $(found "$1")"
or you could use printf.
Putting it all together
I think this is the script you were trying to write:
#!/bin/sh
found () {
grep -q "[[:space:]]$1/" /etc/services
}
port=$1
while found "$port"; do
port=$(($port+1))
done
printf 'found: %dnport %dn' "$(found "$1")" "$port"
Bonus
As I was writing this, my initial reaction was that counting lines matching a given pattern could be done in awk as well as grep. It then occurred to me that the entire program could be written in awk. As a bonus, here is one implementation of that (though here I assume that /etc/services is sorted by port number):
#!/usr/bin/awk -f
BEGIN {
FS = "[ t/][ t/]*"
ARGC = 2
PORT = ARGV[1]
OPORT = PORT
ARGV[1] = "/etc/services"
}
($2 == OPORT) {
FOUND = FOUND + 1
}
(NEXT == 0 && NR > 1 && $2 > PORT) {
NEXT = ($2 > PORT + 1) ? PORT + 1 : NEXT
PORT = $2
}
END {
printf "found: %dnport: %dn", FOUND, (FOUND == 0) ? OPORT : NEXT
}
edited May 5 '17 at 14:12
answered May 4 '17 at 2:40
FoxFox
5,74211233
5,74211233
I got that much. On my system, if you run this with a port number of 22, it will sayfound: 3, port: 24, as in "There were 3 entries for port 22 in /etc/services, and the next available port is 24". Are you looking for something different?
– Fox
May 4 '17 at 3:20
@Kay I originally posted an incorrectawkversion. Then I removed it, but decided to fix it and put it back in. You might try both thebashandawkversions. You'll find that theawkversion is noticeably faster, because it does not need to search through the entire file for each increment ofPORT
– Fox
May 4 '17 at 3:37
I have to object to'[[:space:]]'"$1"'/'on aesthetic grounds. The/aren't special in double quotes, so you could just do"[[:space:]]$1/"and avoid the multi-quote dance...
– ilkkachu
May 4 '17 at 17:00
@ilkkachu After thinking for a while, I agree with you. Edited to universal weak quotes. I was already in violation of my own preference for strong quotes whenever possible with"1"
– Fox
May 5 '17 at 0:05
This would beea lot more idiomatic if the function's exit code was 0 for found, non-zero otherwise. Capturing the output number and comparing it lexically to another is clumsy, inelegant, and error-prone, and a common antipattern.
– tripleee
May 5 '17 at 14:03
add a comment |
I got that much. On my system, if you run this with a port number of 22, it will sayfound: 3, port: 24, as in "There were 3 entries for port 22 in /etc/services, and the next available port is 24". Are you looking for something different?
– Fox
May 4 '17 at 3:20
@Kay I originally posted an incorrectawkversion. Then I removed it, but decided to fix it and put it back in. You might try both thebashandawkversions. You'll find that theawkversion is noticeably faster, because it does not need to search through the entire file for each increment ofPORT
– Fox
May 4 '17 at 3:37
I have to object to'[[:space:]]'"$1"'/'on aesthetic grounds. The/aren't special in double quotes, so you could just do"[[:space:]]$1/"and avoid the multi-quote dance...
– ilkkachu
May 4 '17 at 17:00
@ilkkachu After thinking for a while, I agree with you. Edited to universal weak quotes. I was already in violation of my own preference for strong quotes whenever possible with"1"
– Fox
May 5 '17 at 0:05
This would beea lot more idiomatic if the function's exit code was 0 for found, non-zero otherwise. Capturing the output number and comparing it lexically to another is clumsy, inelegant, and error-prone, and a common antipattern.
– tripleee
May 5 '17 at 14:03
I got that much. On my system, if you run this with a port number of 22, it will say
found: 3, port: 24, as in "There were 3 entries for port 22 in /etc/services, and the next available port is 24". Are you looking for something different?– Fox
May 4 '17 at 3:20
I got that much. On my system, if you run this with a port number of 22, it will say
found: 3, port: 24, as in "There were 3 entries for port 22 in /etc/services, and the next available port is 24". Are you looking for something different?– Fox
May 4 '17 at 3:20
@Kay I originally posted an incorrect
awk version. Then I removed it, but decided to fix it and put it back in. You might try both the bash and awk versions. You'll find that the awk version is noticeably faster, because it does not need to search through the entire file for each increment of PORT– Fox
May 4 '17 at 3:37
@Kay I originally posted an incorrect
awk version. Then I removed it, but decided to fix it and put it back in. You might try both the bash and awk versions. You'll find that the awk version is noticeably faster, because it does not need to search through the entire file for each increment of PORT– Fox
May 4 '17 at 3:37
I have to object to
'[[:space:]]'"$1"'/' on aesthetic grounds. The / aren't special in double quotes, so you could just do "[[:space:]]$1/" and avoid the multi-quote dance...– ilkkachu
May 4 '17 at 17:00
I have to object to
'[[:space:]]'"$1"'/' on aesthetic grounds. The / aren't special in double quotes, so you could just do "[[:space:]]$1/" and avoid the multi-quote dance...– ilkkachu
May 4 '17 at 17:00
@ilkkachu After thinking for a while, I agree with you. Edited to universal weak quotes. I was already in violation of my own preference for strong quotes whenever possible with
"1"– Fox
May 5 '17 at 0:05
@ilkkachu After thinking for a while, I agree with you. Edited to universal weak quotes. I was already in violation of my own preference for strong quotes whenever possible with
"1"– Fox
May 5 '17 at 0:05
This would beea lot more idiomatic if the function's exit code was 0 for found, non-zero otherwise. Capturing the output number and comparing it lexically to another is clumsy, inelegant, and error-prone, and a common antipattern.
– tripleee
May 5 '17 at 14:03
This would beea lot more idiomatic if the function's exit code was 0 for found, non-zero otherwise. Capturing the output number and comparing it lexically to another is clumsy, inelegant, and error-prone, and a common antipattern.
– tripleee
May 5 '17 at 14:03
add a comment |
Minus the math part, here's a quick and dirty POC to accomplish what you're going for, but using an "if" statement in lieu of "while" loop conditions for your logic.
#!/bin/bash
port=$1
echo $port | while read a; do
found=$(cat /etc/services | grep -Eo '[0-9]{1,5}/' | sed 's/^/ /' | grep ' '$a'/')
if [[ $found ]];
then echo port $a is being used
else echo port $a is not being used
fi
done
Edit: The issue was stemming from the $found variable. the single quotes in the grep make $port get interpreted literally, so its grepping for the string
' $port/' instead of ' 80/' for example.
fix: just change the single quotes to double quotes.
found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)
I haven't read this thoroughly, but "grepping for a space in front of the port numbers" is not what[[:space:]]means. That is a POSIX character class that matches any whitespace character, including tab, formfeed, or U2008 (thin space)
– Fox
May 4 '17 at 16:19
@Fox you are right about the [[:space:]], I did not know that. In that case all you need to do is change your single quotes to double quotes to allow string interpolation. The single quotes interpret '$port' as a literal instead of substituting the value, which should be a number in this case.found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)
– TheWhiteCong
May 4 '17 at 16:41
add a comment |
Minus the math part, here's a quick and dirty POC to accomplish what you're going for, but using an "if" statement in lieu of "while" loop conditions for your logic.
#!/bin/bash
port=$1
echo $port | while read a; do
found=$(cat /etc/services | grep -Eo '[0-9]{1,5}/' | sed 's/^/ /' | grep ' '$a'/')
if [[ $found ]];
then echo port $a is being used
else echo port $a is not being used
fi
done
Edit: The issue was stemming from the $found variable. the single quotes in the grep make $port get interpreted literally, so its grepping for the string
' $port/' instead of ' 80/' for example.
fix: just change the single quotes to double quotes.
found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)
I haven't read this thoroughly, but "grepping for a space in front of the port numbers" is not what[[:space:]]means. That is a POSIX character class that matches any whitespace character, including tab, formfeed, or U2008 (thin space)
– Fox
May 4 '17 at 16:19
@Fox you are right about the [[:space:]], I did not know that. In that case all you need to do is change your single quotes to double quotes to allow string interpolation. The single quotes interpret '$port' as a literal instead of substituting the value, which should be a number in this case.found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)
– TheWhiteCong
May 4 '17 at 16:41
add a comment |
Minus the math part, here's a quick and dirty POC to accomplish what you're going for, but using an "if" statement in lieu of "while" loop conditions for your logic.
#!/bin/bash
port=$1
echo $port | while read a; do
found=$(cat /etc/services | grep -Eo '[0-9]{1,5}/' | sed 's/^/ /' | grep ' '$a'/')
if [[ $found ]];
then echo port $a is being used
else echo port $a is not being used
fi
done
Edit: The issue was stemming from the $found variable. the single quotes in the grep make $port get interpreted literally, so its grepping for the string
' $port/' instead of ' 80/' for example.
fix: just change the single quotes to double quotes.
found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)
Minus the math part, here's a quick and dirty POC to accomplish what you're going for, but using an "if" statement in lieu of "while" loop conditions for your logic.
#!/bin/bash
port=$1
echo $port | while read a; do
found=$(cat /etc/services | grep -Eo '[0-9]{1,5}/' | sed 's/^/ /' | grep ' '$a'/')
if [[ $found ]];
then echo port $a is being used
else echo port $a is not being used
fi
done
Edit: The issue was stemming from the $found variable. the single quotes in the grep make $port get interpreted literally, so its grepping for the string
' $port/' instead of ' 80/' for example.
fix: just change the single quotes to double quotes.
found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)
edited 1 hour ago
Rui F Ribeiro
41.9k1483142
41.9k1483142
answered May 4 '17 at 15:30
TheWhiteCongTheWhiteCong
94
94
I haven't read this thoroughly, but "grepping for a space in front of the port numbers" is not what[[:space:]]means. That is a POSIX character class that matches any whitespace character, including tab, formfeed, or U2008 (thin space)
– Fox
May 4 '17 at 16:19
@Fox you are right about the [[:space:]], I did not know that. In that case all you need to do is change your single quotes to double quotes to allow string interpolation. The single quotes interpret '$port' as a literal instead of substituting the value, which should be a number in this case.found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)
– TheWhiteCong
May 4 '17 at 16:41
add a comment |
I haven't read this thoroughly, but "grepping for a space in front of the port numbers" is not what[[:space:]]means. That is a POSIX character class that matches any whitespace character, including tab, formfeed, or U2008 (thin space)
– Fox
May 4 '17 at 16:19
@Fox you are right about the [[:space:]], I did not know that. In that case all you need to do is change your single quotes to double quotes to allow string interpolation. The single quotes interpret '$port' as a literal instead of substituting the value, which should be a number in this case.found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)
– TheWhiteCong
May 4 '17 at 16:41
I haven't read this thoroughly, but "grepping for a space in front of the port numbers" is not what
[[:space:]] means. That is a POSIX character class that matches any whitespace character, including tab, formfeed, or U2008 (thin space)– Fox
May 4 '17 at 16:19
I haven't read this thoroughly, but "grepping for a space in front of the port numbers" is not what
[[:space:]] means. That is a POSIX character class that matches any whitespace character, including tab, formfeed, or U2008 (thin space)– Fox
May 4 '17 at 16:19
@Fox you are right about the [[:space:]], I did not know that. In that case all you need to do is change your single quotes to double quotes to allow string interpolation. The single quotes interpret '$port' as a literal instead of substituting the value, which should be a number in this case.
found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)– TheWhiteCong
May 4 '17 at 16:41
@Fox you are right about the [[:space:]], I did not know that. In that case all you need to do is change your single quotes to double quotes to allow string interpolation. The single quotes interpret '$port' as a literal instead of substituting the value, which should be a number in this case.
found=$(cat /etc/services | grep -o "[[:space:]]$port/" | wc -l)– TheWhiteCong
May 4 '17 at 16:41
add a comment |
Thanks for contributing an answer to Unix & Linux Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f362921%2fwhat-am-i-doing-wrong-trying-to-write-a-bash-script-that-returns-the-number-of-t%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
You're not reinitializing found inside the loop, and you can't assign $found; you want found= ...
– Jeff Schaller♦
May 4 '17 at 0:22
2
Plug for shellcheck.net for the syntactic error checking
– Jeff Schaller♦
May 4 '17 at 0:24
I have transcribed your image to text. For a bit of rationale behind this, see this Meta post
– Fox
May 4 '17 at 1:49
1
Related: unix.stackexchange.com/questions/180492/…
– Kusalananda♦
May 4 '17 at 6:38
I wonder if you meant for the
grepcheck to be done within the loop, now you'd just return the next number even if that was reserved too.– ilkkachu
May 4 '17 at 16:58