My problem is pretty simple. I have :
a=$(echo "lol")
for i in {1..3};
do
echo $a && echo $i ;
done
I get :
lol
1
lol
2
lol
3
I would like to print only once the variable a at the beginning of the output , to get :
lol
1
2
3
Any idea?
You don't need a loop at all
a=$(echo "lol") # Not sure why poster wrote this rater than a=lol
printf %s\\n "$a" {1..3}
I suggest:
#!/bin/bash
a="lol"$'\n' # append newline
for i in {1..3}
do
echo -e "$a$i" # -e: enable interpretation of escape sequences
unset a
done
Or replace in your question
echo $a && echo $i ;
with
[[ "$i" == "1" ]] && echo "$a"
echo "$i"
See: help echo and help unset
Move the echo outside of the for loop
a=$(echo "lol")
echo $a
for i in {1..3}; do
echo $i;
done
or:
echo "lol"
for i in {1..3}; do
echo $i;
done
test run in shell
Related
I am writing a bash script to finger the first three line of user's info.
ex:
$ ./c.sh bob unknown
Login: bob Name: Bob
Directory: /u1/h7/bob Shell: /bin/tcsh
Office: AA 044, x8361 Home Phone: 000-000-0000
unknown: no such user.
Here is my code so far
#!/bin/bash
if [ $# == 0 ]; then
echo "Usage: ./c.sh Login/Username"
exit
else
i=$#
j=1
while [ "$j" -le "$i" ]; do
finger ${$j} | head -n+3
echo
j=$(($j+1))
done
fi
instead of giving what user types for the command line arguments, ${$j} is giving me the the value of $j, any suggestion and help for how to get the login/username? I've tried $($j), $((j)), ${$j}....
The easy answer: stop using unnecessary indirection:
#!/bin/bash
if (( $# == 0 )); then
echo "Usage: ./c.sh Login/Username"
exit
else
while [[ $1 ]]; do
finger "$1" | head -n+3
echo
shift
done
fi
or…
…
for user; do # equivalent to `for user in "$#"; do`
finger "$user" | head -n+3
…
done
You could write it this way:
i=$#
j=1
while [ $j -le $i ]; do
finger "${#:j++:1}" | head -n+3
echo
done
…but you don't need to work that hard.
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo "Usage: $0 Login/Username"
exit
else
for ARG in "$#"; do
finger "$ARG" | head -n 3
echo # If you want a newline
done
fi
As simple as it can be.
#!/bin/bash
a=coop; b=(`echo $a | sed 's/\(.\)/\1\n/g'`)
for i in ${b[#]}
do
echo -n $i
count=$((count+1))
if [ $count = 2 ]; then
echo -e '\e[0;34m'$i
shift
echo -ne $*'\e[0m'
fi
done
Output: cooop (the middle one is in blue). What I want the script to do is show the exact word stored in the variable named "a". But as you can see, another "o" is added next to "p". So how can i go about removing the extra letter?
Try this:
#!/bin/bash
blue='\e[0;34m'
nc='\e[0m'
a=coop
b=($(echo $a | sed 's/\(.\)/\1\n/g'))
count=0
for i in ${b[#]}; do
if [ $count = 2 ]; then
echo -ne "${blue}${i}"
echo -ne "${nc}"
else
echo -n "$i"
fi
count=$((count+1))
done
I'm trying to implement a REPL (read-eval-print loop) in bash. If such a thing already exists, please ignore the following and answer this question with a pointer to it.
Let's use this script as an example (name it test.sh):
if true
then
echo a
else
echo b
fi
echo c
What I want to do is to read this script line by line, check if what I have read so far is a complete bash expression; if it is complete, eval it; otherwise keep on reading the next line. The script below illustrates my idea hopefully (it does not quite work, though).
x=""
while read -r line
do
x=$x$'\n'$line # concatenate by \n
# the line below is certainly a bad way to go
if eval $x 2>/dev/null; then
eval $x # code seems to be working, so eval it
x="" # empty x, and start collecting code again
else
echo 'incomplete expression'
fi
done < test.sh
Motivation
For a bash script, I want to parse it into syntactically complete expressions, evaluate each expression, capture the output, and finally mark up the source code and output (say, using Markdown/HTML/LaTeX/...). For example, for a script
echo a
echo b
What I want to achieve is the output like this:
```bash
echo a
```
```
a
```
```bash
echo b
```
```
b
```
instead of evaluating the whole script and capture all the output:
```bash
echo a
echo b
```
```
a
b
```
bash -n -c "$command_text"
...will determine whether your $command_text is a syntactically valid script without actually executing it.
Note that there's a huge breadth of space between "syntactically valid" and "correct". Consider adopting something like http://shellcheck.net/ if you want to properly parse the language.
The following scripts should generate the Markdown output you expect.
eval "set -n; $x" is used to verify if the command is complete, by checking for syntax errors in the command. Only a command that has no syntax errors will be considered complete, executed, and shown in the output Markdown.
Please note that the input script that is to be processed is executed in a sub-shell and therefore will not interfere with the processing script itself (i.e. the input script can use the same variable names as the processing script and cannot change the values of variables in the processing script). The only exception are the special variables called ___internal__variable___.
There are two approaches to how to achieve that, which I present below. In Version 1, whenever a new complete command is processed, all the statements before it are executed to create a "context" for the command. This effectively runs the input script multiple times.
In Version 2, the environment of the sub-shell is stored in a variable after each complete command is executed. Then, before the next command is executed, the previous environment is restored in the sub-shell.
Version 1
#!/bin/bash
x="" # Current
y="" # Context
while IFS= read -r line # Keep indentation
do
[ -z "$line" ] && continue # Skip empty lines
x=$x$'\n'$line # Build a complete command
# Check current command for syntax errors
if (eval "set -n; $x" 2> /dev/null)
then
# Run the input script up to the current command
# Run context first and ignore the output
___internal_variable___="$x"
out=$(eval "$y" &>/dev/null; eval "$___internal_variable___")
# Generate command markdown
echo "=================="
echo
echo "\`\`\`bash$x"
echo "\`\`\`"
echo
# Generate output markdown
if [ -n "$out" ]
then
echo "Output:"
echo
echo "\`\`\`"
echo "$out"
echo "\`\`\`"
echo
fi
y=$y$'\n'$line # Build context
x="" # Clear command
fi
done < input.sh
Version 2
#!/bin/bash
x="" # Current command
y="true" # Saved environment
while IFS= read -r line # Keep indentation
do
[ -z "$line" ] && continue # Skip empty lines
x=$x$'\n'$line # Build a complete command
# Check current command for syntax errors
if (eval "set -n; $x" 2> /dev/null)
then
# Run the current command in the previously saved environment
# Then store the output of the command as well as the new environment
___internal_variable_1___="$x" # The current command
___internal_variable_2___="$y" # Previously saved environment
out=$(bash -c "${___internal_variable_2___}; printf '<<<BEGIN>>>'; ${___internal_variable_1___}; printf '<<<END>>>'; declare -p" 2>&1)
# Separate the environment description from the command output
y="${out#*<<<END>>>}"
out="${out%%<<<END>>>*}"
out="${out#*<<<BEGIN>>>}"
# Generate command markdown
echo "=================="
echo
echo "\`\`\`bash$x"
echo "\`\`\`"
echo
# Generate output markdown
if [ -n "$out" ]
then
echo "Output:"
echo
echo "\`\`\`"
echo "$out"
echo "\`\`\`"
echo
fi
x="" # Clear command
fi
done < input.sh
Example
For input script input.sh:
x=10
echo "$x"
y=$(($x+1))
echo "$y"
while [ "$y" -gt "0" ]
do
echo $y
y=$(($y-1))
done
The output will be:
==================
```bash
x=10
```
==================
```bash
echo "$x"
```
Output:
```
10
```
==================
```bash
y=$(($x+1))
```
==================
```bash
echo "$y"
```
Output:
```
11
```
==================
```bash
while [ "$y" -gt "0" ]
do
echo $y
y=$(($y-1))
done
```
Output:
```
11
10
9
8
7
6
5
4
3
2
1
```
Assume your test commands are stored in a file called "example". That is, using same commands than in previous answer:
$ cat example
x=3
echo "$x"
y=$(($x+1))
echo "$y"
while [ "$y" -gt "0" ]
do
echo $y
y=$(($y-1))
done
the command:
$ (echo 'PS1=; PROMPT_COMMAND="echo -n =====; echo"'; cat example2 ) | bash -i
produces:
=====
x=3
=====
echo "$x"
3
=====
y=$(($x+1))
=====
echo "$y"
4
=====
=====
=====
while [ "$y" -gt "0" ]
> do
> echo $y
> y=$(($y-1))
> done
4
3
2
1
=====
exit
if you are interested also in the intermediate results of a loop, the command:
$ ( echo 'trap '"'"'echo; echo command: $BASH_COMMAND; echo answer:'"'"' DEBUG'; cat example ) | bash
results in:
command: x=3
answer:
command: echo "$x"
answer:
3
command: y=$(($x+1))
answer:
command: echo "$y"
answer:
4
command: [ "$y" -gt "0" ]
answer:
command: echo $y
answer:
4
command: y=$(($y-1))
answer:
command: [ "$y" -gt "0" ]
answer:
command: echo $y
answer:
3
command: y=$(($y-1))
answer:
command: [ "$y" -gt "0" ]
answer:
command: echo $y
answer:
2
command: y=$(($y-1))
answer:
command: [ "$y" -gt "0" ]
answer:
command: echo $y
answer:
1
command: y=$(($y-1))
answer:
command: [ "$y" -gt "0" ]
answer:
Addendum 1
It is not difficult to change the previous results to some other format. By example, this small perl script:
$ cat formatter.pl
#!/usr/bin/perl
#
$state=4; # 0: answer, 1: first line command, 2: more command, 4: unknown
while(<>) {
# print $state;
if( /^===COMMAND===/ ) {
print "===\n";
$state=1;
next;
}
if( $state == 1 ) {
print;
$state=2;
next;
}
if( $state == 2 && /^>+ (.*)/ ) {
print "$1\n";
next;
}
if( $state == 2 ) {
print "---\n";
$state=0;
redo;
}
if( $state == 0 ) {
print;
next;
}
}
when used in command:
( echo 'PS1="===COMMAND===\n"'; cat example ) | bash -i 2>&1 | ./formatter.pl
gives this result:
===
x=3
===
echo "$x"
---
3
===
y=$(($x+1))
===
echo "$y"
---
4
===
===
===
while [ "$y" -gt "0" ]
do
echo $y
y=$(($y-1))
done
---
4
3
2
1
===
exit
In lieu of pidfiles, as long as your script has a uniquely identifiable name you can do something like this:
#!/bin/bash
COMMAND=$0
# exit if I am already running
RUNNING=`ps --no-headers -C${COMMAND} | wc -l`
if [ ${RUNNING} -gt 1 ]; then
echo "Previous ${COMMAND} is still running."
exit 1
fi
... rest of script ...
I would like to split a line into words. I know this can be done with this
For word in $line; do echo $word; done
But I want to make group of 3-3 words. So my question is, how can I split a line in group of 3-3 words ?
For example
Input : I am writing this line for testing the code.
Output :
I am writing
this line for
testing the code.
Read the words three at a time. Set the line being read from to the remainder:
while read -r remainder
do
while [[ -n $remainder ]]
do
read -r a b c remainder <<< "$remainder"
echo "$a $b $c"
done
done < inputfile
What about paste command
for word in $line; do echo $word; done | paste - - -
for word in $line; do echo $word; done | paste -d" " - - -
Easy regex exercise.
sed -e "s/\([^\ ]*\ [^\ ]*\ [^\ ]*\)\ /\1\\`echo -e '\n\r'`/g"
The only tricky part was getting the new line in the sed, as there isn't a standard for that.
$ echo "I am writing this line for testing the code."|sed -e "s/\([^\ ]*\ [^\ ]*\ [^\ ]*\)\ /\1\\`echo -e '\n\r'`/g"
I am writing
this line for
testing the code.
You're welcome.
Just use set to set your input as positional arguments, and process them in groups of three. That way you don't need anything fancy or bash-specific:
line="I am writing this line for testing the code."
set junk $line
shift
while [ $# -ge 3 ]; do
echo "Three words: $1 $2 $3"
shift 3
done
As a start you can use this, which reads every word into an array
#!/bin/bash
total=0
while read
do
for word in $REPLY
do
A[$total]=$word
total=$(($total+1))
done
done < input.txt
for i in "${A[#]}"
do
echo $i
done
Next step is to use seq or similar to loop through the array and print it in groups of three.
There's a non-generic straight forward solution:
#!/bin/bash
path_to_file=$1
while read line
do
counter=1;
for word in $line
do
echo -n $word" ";
if (($counter % 3 == 0))
then
echo "";
fi
let counter=counter+1;
done
done < ${path_to_file}
Save that in a script, give it a name (test.sh for example) and set it to execution mode. Than if your text is saved in "myfile.txt" call it like this:
test.sh myfile.txt
Here's an example of possible solution.
#!/bin/bash
line="I am writing this line for testing the code."
i=0
for word in $line; do
((++i))
if [[ $i -eq 3 ]]; then
i=0
echo "$word"
else
echo -ne "$word "
fi
done
My code:
#!/bin/bash
for i in $#;
do echo $i;
done;
run script:
# ./script 1 2 3
1
2
3
So, I want to skip the first argument and get:
# ./script 1 2 3
2
3
Use the offset parameter expansion
#!/bin/bash
for i in "${#:2}"; do
echo $i
done
Example
$ func(){ for i in "${#:2}"; do echo "$i"; done;}; func one two three
two
three
Use shift command:
FIRST_ARG="$1"
shift
REST_ARGS="$#"
Look into Parameter Expansions in the bash manpage.
#/bin/bash
for i in "${#:2}"
do echo $i
done
You could just have a variable testing whether it's the first argument with something like this (untested):
#!/bin/bash
FIRST=1
for i in $#
do
if [ FIRST -eq 1 ]
then
FIRST=0
else
echo $i
fi
done