I'm Brand new to Rust and I have been writing some practice apps. I am trying to accept command-line arguments using Clap. The code below takes a string and a number and prints them back out, as such:
$ cargo run "this is a test" -n11
this is a test
11
This works fine, but I want to be able to pipe input in place of the string like this:
$ echo "this is a test" | cargo run -- -n11
this is a test
11
Trying this yields:
error: The following required arguments were not provided:
<INPUT>
USAGE:
clap_sample --num <num> <INPUT>
For more information try --help
I can get around this using xargs like this:
$ echo "this is a test" | xargs -d '\n' cargo run -- -n11
Is there a better way to do this so that I can accept piped in strings while still using the -n option? Thanks in advance.
use clap::{Arg, App};
fn main() {
let matches = App::new("Clap Sample")
.arg(Arg::new("INPUT")
.required(true)
.index(1))
.arg(Arg::new("num")
.short('n')
.long("num")
.takes_value(true))
.get_matches();
let usr_string = matches.value_of("INPUT").unwrap();
let key: u8 = matches.value_of("num").unwrap()
.parse()
.expect("NaN :(");
println!("{}", usr_string);
println!("{}", key);
}
Bonus question:
If I pipe a string in with xargs, I can have newlines in the string (with delimiter set to \0) and they are reflected in the output. If I pass it directly without echo and xargs, a literal '\n' shows in the output. Is there a way to have the newline represented when run directly?
Your code is checking the command line for arguments, it is not reading standard input. To use xargs get move the input from the pipe to the command line is a good way of doing it.
echo -n "this is a test" | xargs cargo run -- -n11
The other option you have is to change your program so it reads from stdin if no user_string argument was given. Here is a good starting point to read stdin https://doc.rust-lang.org/std/io/struct.Stdin.html
You should also replace the unwrap() here:
let key: u8 = matches.value_of("num").unwrap()
with a check if the argument was given as it was not .required(true) for instance with a
if let Some(key) = matches.value_of("num")
or maybe with a unwrap_or("0")
Related
i want do something like that:
$ ( foo1.sh $1 | foo2.sh | bar.sh |) arg
sometime pipe is fast than write function, but i can pass parameters/args between pipe
now: I'm triad to search for package firefox by xbps but i get this error bellow:
$ xbps-query -Rs $1 | fzf firefox
xbps-query: option requires an argument -- 's'
unknown option: firefox
Pipelines don't have arguments. Instead, write a script or function that takes arguments and runs the pipeline you want:
foo() {
xbps-query -Rs "$1" | fzf
}
You can then run it with foo firefox
So I'm getting to get the CPU core temperature using sensors command.
Inside conky, I wrote
$Core 0 Temp:$alignr${execi 1 sensors | grep 'Core 0' | awk {'print $3'}} $alignr${execibar 1 sensors | grep 'Core 0' | awk {'print $3'}}
Each second I'm running the exact same command sensors | grep 'Core 0' | awk {'print $3'} in two places for exact same output. Is there is a way to hold the output inside a variable and use that variable in place of the commands.
conky does not have user variables. What you can do instead is call lua from conky to do this for you. The lua language is usually built-in to conky by default, so you need only put some code in a file, include the file in the conky setup file, and call the function. For example, these shell commands will create a test:
cat >/tmp/.conkyrc <<\!
conky.config = {
lua_load = '/tmp/myfunction.lua',
minimum_height = 400,
minimum_width = 600,
use_xft = true,
font = 'Times:size=20',
};
conky.text = [[
set ${lua myfunction t ${execi 1 sensors | awk '/^Core 0/{print 0+$3}'}}°C
get ${lua myfunction t}°C ${lua_bar myfunction t}
]]
!
cat >/tmp/myfunction.lua <<\!
vars = {}
function conky_myfunction(varname, arg)
if(arg~=nil)then vars[varname] = conky_parse(arg) end
return vars[varname]
end
!
conky -c /tmp/.conkyrc -o
In the myfunction.lua file, we declare a function myfunction() (which needs to be prefixed conky_ so we can call it from conky). It takes 2 parameters, the name of a variable, and a conky expression. It calls conky_parse() to evaluate the expression, and saves the value in a table vars, under the name provided by the caller. It then returns the resulting value to the caller. If no expression was given, it will return the previous value.
In the conky.text the line beginning set calls myfunction in lua with the arbitrary name of a variable, t, and the execi sensors expression to evaluate, save, and return. The line beginning get calls myfunction to just get the value.
lua_bar is similar to exec_bar, but calls a lua function, see man conky. However, it expects a number without the leading + that exec_bar accepts, so I've changed the awk to return this, and have added the °C to the conky text instead.
I'm trying to use "sort -V" command (aka version-sort) in a sh file.
Specifically, I have the following line of code in a sh file:
SOME_PATH="$(ls dir_1/dir_2/v*/filename.txt | sort -V | tail -n1)"
What I'm trying to accomplish through the above command is that given a list of file paths with different version numbers, I want to get the file path with the greatest version number.
For example, let's assume that I have the following list of file paths:
dir_1/dir_2/v1/filename.txt,
dir_1/dir_2/v2/filename.txt,
dir_1/dir_2/v11/filename.txt
Then, I want the command to return dir_1/dir_2/v11/filename.txt instead of dir_1/dir_2/v2/filename.txt since the former has the greatest version value, "11".
From my understanding the above linux command precisely accomplishes this.
I confirmed it working on the Linux bash terminal.
However, when I run a sh file with the above command in it, I'm getting a
"ERROR: Unknown command line flag 'V'" error message.
Is there a way to make version-sort work in a sh file?
If not, is there a way to implement it not using -V flag?
Thank you.
Using shell's printf and awk:
SOME_PATH=$(printf %s\\0 dir_1/dir_2/v*/filename.txt |
awk 'BEGIN{FS="/";RS="\0";v=0}{match($3,/v([[:digit:]]+)/,m);if(m[1]>v){v=m[1];l=$0}}END{print l}')
Using awk only:
SOME_PATH=$(awk 'BEGIN{delete ARGV[0];v=0;for(i in ARGV){split(ARGV[i],s,"/");match(s[3],/v([[:digit:]]+)/,m);if(m[1]>v){v=m[1];l=ARGV[i]}}}END{print l}' dir_1/dir_2/v*/filename.txt)
Formatted awk script:
#!/usr/bin/env -S awk -f
BEGIN {
delete ARGV[0]
v=0
for (i in ARGV) {
split(ARGV[i], s, "/")
match(s[3], /v([[:digit:]]+)/, m)
if (m[1]>v) {
v=m[1]
l=ARGV[i]
}
}
}
END {
print l
}
Using a null delimited list stream, and not parsing the output of ls 1:
SOME_PATH=$(
printf '%s\0' dir_1/dir_2/v*/filename.txt |
sort -z -t'/' -k3V |
tail -zn1 |
tr -d '\0'
)
How it works:
printf '%s\0' dir_1/dir_2/v*/filename.txt: Expands the paths into a null delimited stream output.
sort -z -t'/' -k3V: Sorts the null delimited input stream on -k3V version number from the 3rd column, -t'/' using / as a delimiter.
tail -zn1: Outputs the least null delimited entry from the input stream.
tr -d '\0': Trim-out any remaining null to prevent the shell from complaining with error: warning: command substitution: ignored null byte in input.
StackExchange: Why not parse ls (and what to do instead)?
I am trying to extract AAA and BBB from the output of the command "dspmq".
$dspmq <- this command gives output as -->
QMNAME(AAA) STATUS(Running)
QMNAME(BBB) STATUS(Running)
But it doesn't work with the below code.
perl -e 'use Data::Dumper qw(Dumper);my #qmgrlist = `dspmq`;$size = #qmgrlist;foreach my $i (#qmgrlist){my #temp1 = split /QMNAME\(/, $i;print #temp1;}'
AAA) STATUS(Running)
BBB) STATUS(Running)
I am able to truncate "QMNAME(" but unable to truncate those to the right of AAA and BBB. Basically I want to get the string between "QMNAME(" and the immediate ")". Please assist.
I think a regex approach is better than split() here, but you could use split() by splitting on parentheses and taking the second item in the returned list.
for (#qmgrlist) {
say +(split /[()]/)[0];
}
And a brief note on your use of command-line options to run this code. You can make it simpler if you a) pipe the output of qspmq into your code and b) use -n to process a record at a time.
$ perl -nE 'say +(split /[()]/)[1]' `dspmq`
There's also -M to load modules (e.g. -MData::Dumper), but you don't seem to be using Data::Dumper any more.
split isn't going to do what you need. I would just use a regular expression to match the sub-string you need
So change the loop from this
foreach my $i (#qmgrlist)
{
my #temp1 = split /QMNAME\(/, $i;
print #temp1;
}
to this
foreach my $i (#qmgrlist)
{
print "$1\n"
if /QMNAME\((.+?)\)/;
}
Try this perl one-liner:
dspmq | perl -lne 'print for m{ QMNAME [(] ( [^)]* ) [)] }x'
Here, dspmq STDOUT is fed using a pipe | into STDIN of the perl code, which has these flags:
-e tells Perl interpreter to look for the code inline rather than in a separate script file.
-n feeds the input line by line to the inline code (this way you do not need to store the output in an array - this matters for large outputs, not in your case).
-l strips the input record separator (newline on *NIX) before feeding it to the code, and appends it automatically after during print.
The print ... for ... m{... (...) ...} code prints every pattern captured in parentheses.
The captured pattern is [^)]*, which is maximum number (0 or more) chars that are not (^) listed in the character class, that is, that are not closing parens.
[(] ... [)] are literal parentheses escaped as character classes for readability. I prefer this to escaping like so: \( ... \).
QMNAME is used to make the programmer's intentions clear: you want the string that follows QMNAME in parens. I prefer this to using the field index, such as 1, which protects you against minor variation in output of your command used with different options, on different systems, etc.
Finally, the x regex modifier in m{...}x enables comments and whitespace to be ignored, and is preferred for readability.
RELATED:
Cutting the output of a dspmq command
Desired output can be achieved with following code
use strict;
use warnings;
use feature 'say';
map{ say $1 if /QMNAME\((.+?)\)/ } <DATA>;
__DATA__
QMNAME(AAA) STATUS(Running)
QMNAME(BBB) STATUS(Running)
output
AAA
BBB
and one liner (not tested - I am on Windows computer)
dspmq | perl -lne 'print $1 if /QMNAME\((.+?)\)/'
hi trying to replace the following string with a long one :
#x#
with string that I got from the command line:
read test
sed -i --backup 's/#x#/'${test}'/g' file.json README.md
but it is working only for 1 word, it is not working if there is space between word . even between quotes
sed: 1: "s/#x#/string test string: unterminated substitute in regular expression
if case you run it on MacOS and struggling with "unterminated substitute in regular expression", there is an easier explanation for this:
MacOS has slightly other version of sed than usually is on linux. -i requires a parameter. If you have none, just add "" after -i
sed -i "" --backup 's/#x#/'${test}'/g' file.json README.md
or for example if you just have to delete dome line, this works on linux, but brings “invalid command code” on MacOS
sed -i 39d filenamehere.log
and this works on MacOS
sed -i "" 39d filenamehere.log
The problem originates from the way you are using the single-quotes. Currently you are terminating your input behind the 2. single-quote. See the Error message, it makes you aware of the fact that it is missing something.
If you have a file with the following content:
foo #x# foo
Than you can replace the content e.g. with the following command:
sed 's/#x#/bar foo bar/' foo.txt > foo2.txt
And get:
foo bar foo bar foo
If you need to pass in a variable the comment from Gordon Davisson shows you the right way.
By the way, if you want to use the inplace option, on my linux you would need to use the command like this:
sed -i.old "s/#x#/${test}/" foo.txt
But I think this might depends on your enviroment (mac?).
sed doesn't understand strings where a string is a series of literal characters. It replaces a regexp (not a string) with a backreference-enabled "string" (also not a string) all within a set of delimiters (which ALSO require careful handling in both the regexp and the replacement). See Is it possible to escape regex metacharacters reliably with sed for more info.
To replace a string with another string the simplest approach is to just use a tool that understands strings such as awk:
$ cat file
before stuff
foo #x# bar
after stuff
$ cat tst.awk
BEGIN {
old = ARGV[1]
new = ARGV[2]
ARGV[1] = ARGV[2] = ""
}
s = index($0,old) { $0 = substr($0,1,s-1) new substr($0,s+length(old)) }
{ print }
$ test='a/\t/&"b'
$ awk -f tst.awk '#x#' "$test" file
before stuff
foo a/\t/&"b bar
after stuff
The above will work no matter what characters test contains, even newlines:
$ test='contains a
newline'
$ awk -f tst.awk '#x#' "$test" file
before stuff
foo contains a
newline bar
after stuff