So I've written a code to compare if a certain number within the file name is bigger than 11 and if it is than it should make a directory.
-->Mainfolder
-->Jan
-->Huistaak1-HelloWorld_Jonas.De Preter.s.ua_poging_2019-11-12
-->Feremans
-->Huistaak1-HelloWorld_Len.Feremans.s.ua_poging_2019-11-10
...
The code needs to get the day of the provided date
and if it's above 11 it creates a directory "late_inzending"
So it should look like this
-->Mainfolder
-->Jan
-->Huistaak1-HelloWorld_Jonas.De Preter.s.ua_poging_2019-11-12
-->late_inzending
...
My code doesn't seem to work
for dir in */
do
cut1=$(echo "$dir" | cut -f4 -d '_')
cut2=$(echo "$cut1" | cut -f3 -d '-')
declare -i x="$cut2"
declare -i y=11
if (( x > y))
then
mkdir late_inzending
fi
done
Something like.
#!/usr/bin/env bash
for d in ./*/*/; do #: From main plus one level down
[[ ! -d "$d" ]] && continue #: If it is not a directory, skip it.
end="${d##*-}" #: To remain only the last 2 strings and a /, e.g. 12/
(( ${end%/} > 11 )) && #: Remove the trailing `/`, to remain only 12 and compare.
echo mkdir -p "$d"late_inzending #: Append the desired string to the directory and create it.
done
Execute from within main
Remove the echo if you're ok with the ouput.
Resources for the answer above
Parameter Expansion
Shell Arithmetic
Conditional Constructs
help test
Related
I wrote small script under Debian Linux 11 that should check how many instances of application is currently running and what is power usage of GPU cards.
I save it under name test , and she is started every time I access instance over SSH
#!/bin/sh
clear
a=$(nvidia-smi -q -i 0 | grep "Power Draw" | cut -c45-50)
b=$(nvidia-smi -q -i 1 | grep "Power Draw" | cut -c45-50)
c=$(nvidia-smi -q -i 2 | grep "Power Draw" | cut -c45-50)
d=$(nvidia-smi -q -i 3 | grep "Power Draw" | cut -c45-50)
zet=$( echo "$a" + "$b" + "$c" + "$d" | bc -l )
echo "SYSTEM DRAW:" "$zet"
if [ "${zet}" -gt 150 ]; then
echo WARRNING - SYSTEM DRAW LOW
else
echo OK
fi
sleep 8
exit
All I need to add is this line
x=${x%.*}
That convert decimal number in number without decimals and script works perfect.
You could add set -x right before the part you want to debug, which will show you a debug of what is happening in bash. and stop it by inserting after that set +x
like:
set -x
power=$150
echo "SYSTEM DRAW :" $total
if [ $total \> $power ] ; then # escape > otherwise it redirects output
I do not think you are setting the value of $150
The script might be failing if one of the compared values is not set.. so you should initialize your variables to be let' say equal to 0 as a default at the beginning of your script, or via bash
like:
power=${150:-10} # if $150 does not have a value or empty, the default value of $power will be set to `10`
So many possible issues but you can compare possibly decimal values using bc:
if [ "$(echo "$total > $power" | bc)" = 1 ]; then
One problem is that [ (and [[) do string comparisons, and you want a numeric comparison. For that you want to use ((, so something like
if (( $total > 150 )); then
echo "WARNING..."
else
echo "OK"
fi
will work better. Otherwise a total of 1000 would print ok, and 90 would give a warning.
Other problems:
$150 gives you the value of a variable called 150 -- you probably want to remove the $
Outside of special forms like [[ and ((, a > will do an output redirection, rather than being a 'normal' argument to a command such as [.
As the comments recommend, use shellcheck, however, I think your intention is not what you wrote.
Try this, create a script (i.e. myscripy)
#! /bin/bash
power=$150
echo "power=$power"
then run it
./myscript abc
and prints
power=abc50
which is probably very different than what you expect.
That is because power will take the first argument's value ($1) and append 50.
If you wanted argument number 150 (also very unlikely), you should write
power=${150}
but if you want just the number
power=150
edit
based on the comment, use
zet=$(bc <<<"$a+$b+$c+$d")
to calculate zet if the values are floating points.
For the comparison use
if [ "$(bc <<<"$zet>150")" == 1 ]; then
...
fi
I have a text file named creating.txt. In this file, each line has a path.
If line starts with D it means that I have to create directory.
If line starts with F, I have to create a file.
For example:
D:/root/F1/Cat1
F:/root/F1/file1.txt
D:/root/F1/Cat1/Cat2
I need to go line by line, first check if first letter is D or F, then ignore the : and the first letter and accordingly create a file or directory based on given path. If first letter in line is not D or F, just ignore the line.
Also if the file already exists I'm not allowed to create another one.
I tried using awk:
path=`awk { print } creating.txt`
touch $path
But the problem is this reads the whole file (not line by line) so there have to be only one line. Also I don't know how to ignore D: and F:.
It can be done with two 2 one-liners
I reproduced your input :
$ cat creating.txt
D:/root/F1/Cat1
F:/root/F1/file1.txt
D:/root/F1/Cat1/Cat2
You can use this to create the Directories
$ for path in `grep ^D creating.txt | cut -d: -f2`; do mkdir -p "$path";done
Then, for files
$ for file in `grep ^F creating.txt | cut -d: -f2`; do touch "$file";done
Explanation:
The grep, will select the lines beginning [as I used ^] by D (or F)
The cut, will catch the 2nd field using ':' as delimiter
Here is a bash script that will do as you asked:
#!/bin/bash
while IFS= read -r line; do
[[ $line =~ (.):(.*) ]]
[[ ${BASH_REMATCH[1]} == D ]] && mkdir -p "${BASH_REMATCH[2]}"
[[ ${BASH_REMATCH[1]} == F ]] && [[ ! -d ${BASH_REMATCH[2]%/*} ]] && mkdir -p "${BASH_REMATCH[2]%/*}"
[[ ${BASH_REMATCH[1]} == F ]] && touch "${BASH_REMATCH[2]}"
done < creating.txt
The task is to read a file containing letters and numbers. Then put the letters in a text file inside a Letters directory and the numbers in a different text file in a numbers directory. The problem I'm having is I don't know how I would identify strings and integers within a file.
The file I'm using contains:
723
Xavier
Cat
323
Zebra
This is the code I've come up with.
#!/bin/bash
file = $1
mkdir Numbers
mkdir Letters
touch Numbers/Numbers.txt
touch Letters/Letters.txt
for x in $file; do
if [x == string];
then
echo x >> Letters/Letters.txt;
fi
if [x == integer];
then
echo x >> Numbers/Numbers.txt;
fi
Your original code has a lot of syntactical errors. I suggest going forward please submit a code which is at least syntactically correct.
Anyways, assuming you are new to unix shell scripting, here a quick solution to your problem
#!/bin/bash
file=$1
mkdir Numbers
mkdir Letters
touch Numbers/Numbers.txt
touch Letters/Letters.txt
while read line
do
if [[ $line =~ [^0-9] ]]
then
echo $line >> Letters/Letters.txt
else
echo $line >> Numbers/Numbers.txt
fi
done < $file
The question is vague.
This solves the question as posted, and handles lines with mixed
numbers and strings, (e.g.: "a1b2c3".), using tee and bash
process substitution, with tr to delete all undesired chars:
mkdir Numbers Letters
tee >(tr -d '[0-9]' > Letters/Letters.txt) \
>(tr -d '[a-zA-Z]' > Numbers/Numbers.txt) \
> /dev/null < inputfile
If the goal is to remove whole lines that are either all numbers
or all letters, try grep:
mkdir Numbers Letters
tee >(grep -v '^[0-9]*$' > Letters/Letters.txt) \
>(grep '^[0-9]*$' > Numbers/Numbers.txt) \
> /dev/null < inputfile
I wrote a shell script like this:
#! /bin/sh
...
ls | grep "android"
...
and the output is :
android1
android2
xx_android
...
I want to add a number in each file, like this:
1 android1
2 android2
3 XX_android
...
please choose your dir number:
and then wait for the user input line number x, the script reads the line number back then process the corresponding dir. How can we do this in shell ? Thanks !
nl prints line numbers:
ls | grep android | nl
If you pipe the result into cat, you can use the -n option to number each line like so:
ls | grep "android" | cat -n
Pass -n to grep, as follows:
ls | grep -n "android"
From the grep man-page:
-n, --line-number
Prefix each line of output with the line number within its input file.
Instead of implementing the interaction, you can use built-in command select.
select d in $(find . -type d -name '*android*'); do
if [ -n "$d" ]; then
# put your command here
echo "$d selected"
fi
done
The other answers on this page actually don't answer the question 100%. They don't show how to let the user interactively choose the file from another script.
The following approach will allow you to do this, as can be seen in the example. Note that the select_from_list script was pulled from this stackoverflow post
$ ls
android1 android4 android7 mac2 mac5
android2 android5 demo.sh mac3 mac6
android3 android6 mac1 mac4 mac7
$ ./demo.sh
1) android1 3) android3 5) android5 7) android7
2) android2 4) android4 6) android6 8) Quit
Please select an item: 3
Contents of file selected by user: 2.3 Android 1.5 Cupcake (API 3)
Here's the demo.sh and the script it uses to select an item from a list, select_from_list.sh
demo.sh
#!/usr/bin/env bash
# Ask the user to pick a file, and
# cat the file contents if they select a file.
OUTPUT=$(\ls | grep android | select_from_list.sh | xargs cat)
STATUS=$?
# Check if user selected something
if [ $STATUS == 0 ]
then
echo "Contents of file selected by user: $OUTPUT"
else
echo "Cancelled!"
fi
select_from_list.sh
#!/usr/bin/env bash
prompt="Please select an item:"
options=()
if [ -z "$1" ]
then
# Get options from PIPE
input=$(cat /dev/stdin)
while read -r line; do
options+=("$line")
done <<< "$input"
else
# Get options from command line
for var in "$#"
do
options+=("$var")
done
fi
# Close stdin
0<&-
# open /dev/tty as stdin
exec 0</dev/tty
PS3="$prompt "
select opt in "${options[#]}" "Quit" ; do
if (( REPLY == 1 + ${#options[#]} )) ; then
exit 1
elif (( REPLY > 0 && REPLY <= ${#options[#]} )) ; then
break
else
echo "Invalid option. Try another one."
fi
done
echo $opt
This works for me:
line-number=$(ls | grep -n "android" | cut -d: -f 1)
I use this in a script to remove sections of my sitemap.xml which I don't want Googlebot to crawl. I search for the URL (which is unique) and then find the line number using the above. Using simple maths the script then calculates the range of numbers required to delete the entire entry in the XML file.
I agree with jweyrich regarding updating your question to get better answers.
I'm looking for a bash function that will shorten long path names to keep my PS1 variable from getting excessively long. Something along the lines of:
/this/is/the/path/to/a/really/long/directory/i/would/like/shortened
might end up as:
/t../i../t../p../to/a/r../l../d../i/w../like/shortened
something that the took the path and a maximum acceptable number of characters to shorten to would be perfect for my .bashrc file.
Doesn't give the same result, but my ~/.bashrc contains
_PS1 ()
{
local PRE= NAME="$1" LENGTH="$2";
[[ "$NAME" != "${NAME#$HOME/}" || -z "${NAME#$HOME}" ]] &&
PRE+='~' NAME="${NAME#$HOME}" LENGTH=$[LENGTH-1];
((${#NAME}>$LENGTH)) && NAME="/...${NAME:$[${#NAME}-LENGTH+4]}";
echo "$PRE$NAME"
}
PS1='\u#\h:$(_PS1 "$PWD" 20)\$ '
which limits the path shown to 20 characters max. If the path is over 20 characters, it will be shown like /...d/like/shortened or ~/.../like/shortened.
Here's a bash-only solution that you might like. This shortens each part of the path down to the shortest prefix that can still be tab-completed, and uses * instead of .. as the filler.
#!/bin/bash
begin="" # The unshortened beginning of the path.
shortbegin="" # The shortened beginning of the path.
current="" # The section of the path we're currently working on.
end="${2:-$(pwd)}/" # The unmodified rest of the path.
end="${end#/}" # Strip the first /
shortenedpath="$end" # The whole path, to check the length.
maxlength="${1:-0}"
shopt -q nullglob && NGV="-s" || NGV="-u" # Store the value for later.
shopt -s nullglob # Without this, anything that doesn't exist in the filesystem turns into */*/*/...
while [[ "$end" ]] && (( ${#shortenedpath} > maxlength ))
do
current="${end%%/*}" # everything before the first /
end="${end#*/}" # everything after the first /
shortcur="$current"
shortcurstar="$current" # No star if we don't shorten it.
for ((i=${#current}-2; i>=0; i--))
do
subcurrent="${current:0:i}"
matching=("$begin/$subcurrent"*) # Array of all files that start with $subcurrent.
(( ${#matching[*]} != 1 )) && break # Stop shortening if more than one file matches.
shortcur="$subcurrent"
shortcurstar="$subcurrent*"
done
begin="$begin/$current"
shortbegin="$shortbegin/$shortcurstar"
shortenedpath="$shortbegin/$end"
done
shortenedpath="${shortenedpath%/}" # strip trailing /
shortenedpath="${shortenedpath#/}" # strip leading /
echo "/$shortenedpath" # Make sure it starts with /
shopt "$NGV" nullglob # Reset nullglob in case this is being used as a function.
Give it the length as the first argument, and the path as the optional second argument. If no second argument is given, it uses the current working directory.
This will try to shorten to under the length given. If that's not possible, it just gives the shortest path it can give.
Algorithmically speaking, this is probably horrible, but it ends up being pretty fast. (The key to quick shell scripts is avoiding subshells and external commands, especially in inner loops.)
By design, it only shortens by 2 or more characters ('hom*' is just as many characters as 'home').
It's not perfect. There are some situations where it won't shorten as much as is possible, like if there are several files whose filenames share a prefix (If foobar1 and foobar2 exist, foobar3 won't be shortened.)
FYI, there is a built-in \w "shortener" in Bash 4+:
PROMPT_DIRTRIM=3
will shorten /var/lib/whatever/foo/bar/baz to .../foo/bar/baz.
I made some improvements to Evan Krall's code. It now checks to see if your path starts in $HOME and begins the shortened variety with ~/ instead of /h*/u*/
#!/bin/bash
begin="" # The unshortened beginning of the path.
shortbegin="" # The shortened beginning of the path.
current="" # The section of the path we're currently working on.
end="${2:-$(pwd)}/" # The unmodified rest of the path.
if [[ "$end" =~ "$HOME" ]]; then
INHOME=1
end="${end#$HOME}" #strip /home/username from start of string
begin="$HOME" #start expansion from the right spot
else
INHOME=0
fi
end="${end#/}" # Strip the first /
shortenedpath="$end" # The whole path, to check the length.
maxlength="${1:-0}"
shopt -q nullglob && NGV="-s" || NGV="-u" # Store the value for later.
shopt -s nullglob # Without this, anything that doesn't exist in the filesystem turns into */*/*/...
while [[ "$end" ]] && (( ${#shortenedpath} > maxlength ))
do
current="${end%%/*}" # everything before the first /
end="${end#*/}" # everything after the first /
shortcur="$current"
shortcurstar="$current" # No star if we don't shorten it.
for ((i=${#current}-2; i>=0; i--)); do
subcurrent="${current:0:i}"
matching=("$begin/$subcurrent"*) # Array of all files that start with $subcurrent.
(( ${#matching[*]} != 1 )) && break # Stop shortening if more than one file matches.
shortcur="$subcurrent"
shortcurstar="$subcurrent*"
done
#advance
begin="$begin/$current"
shortbegin="$shortbegin/$shortcurstar"
shortenedpath="$shortbegin/$end"
done
shortenedpath="${shortenedpath%/}" # strip trailing /
shortenedpath="${shortenedpath#/}" # strip leading /
if [ $INHOME -eq 1 ]; then
echo "~/$shortenedpath" #make sure it starts with ~/
else
echo "/$shortenedpath" # Make sure it starts with /
fi
shopt "$NGV" nullglob # Reset nullglob in case this is being used as a function.
Also, here are some functions I put in my .bashrc file to shrink the path shown by the shell. I'm not sure if editing $PWD like this is completely safe as some scripts might depend on a valid $PWD string, but so far I haven't had problems with occasional use. Note that I saved the above script as "shortdir" and put it in my PATH.
function tinypwd(){
PWD=`shortdir`
}
function hugepwd(){
PWD=`pwd`
}
EDIT Oct 19 2010
The proper way to do the aliases in bash is by modifying the $PS1 variable; this is how the prompt is parsed. In MOST cases (99% of the time) the current path is in the prompt string as "\w". We can use sed to replace this with shortdir, like so:
#NOTE: trailing space before the closing double-quote (") is a must!!
function tinypwd(){
PS1="$(echo $PS1 | sed 's/\\w/\`shortdir\`/g') "
}
function hugepwd(){
PS1="$(echo $PS1 | sed 's/[`]shortdir[`]/\\w/g') "
}
How about a Python script? This shortens the longest directory names first, one character at a time until it meets its length goal or cannot get the path any shorter. It does not shorten the last directory in the path.
(I started writing this in plain shell script but man, bash stinks at string manipulation.)
#!/usr/bin/env python
import sys
try:
path = sys.argv[1]
length = int(sys.argv[2])
except:
print >>sys.stderr, "Usage: $0 <path> <length>"
sys.exit(1)
while len(path) > length:
dirs = path.split("/");
# Find the longest directory in the path.
max_index = -1
max_length = 3
for i in range(len(dirs) - 1):
if len(dirs[i]) > max_length:
max_index = i
max_length = len(dirs[i])
# Shorten it by one character.
if max_index >= 0:
dirs[max_index] = dirs[max_index][:max_length-3] + ".."
path = "/".join(dirs)
# Didn't find anything to shorten. This is as good as it gets.
else:
break
print path
Example output:
$ echo $DIR
/this/is/the/path/to/a/really/long/directory/i/would/like/shortened
$ ./shorten.py $DIR 70
/this/is/the/path/to/a/really/long/directory/i/would/like/shortened
$ ./shorten.py $DIR 65
/this/is/the/path/to/a/really/long/direc../i/would/like/shortened
$ ./shorten.py $DIR 60
/this/is/the/path/to/a/re../long/di../i/would/like/shortened
$ ./shorten.py $DIR 55
/t../is/the/p../to/a/r../l../di../i/wo../like/shortened
$ ./shorten.py $DIR 50
/t../is/the/p../to/a/r../l../d../i/w../l../shortened
Here's another spin on Evan's answer:
This one uses plus (+) instead of an asterisk (*) for truncated paths. It replaces the HOME path with ~, and it leaves the final directory segment intact. If the final segment is over 20 characters, it shortens it to the tab-completable bit and adds an ellipses (...).
#!/bin/bash
# Modified from http://stackoverflow.com/a/1617048/359287
# By Alan Christopher Thomas (http://alanct.com)
__pwd_ps1 ()
{
begin=""
homebegin=""
shortbegin=""
current=""
end="${2:-$(pwd)}/" # The unmodified rest of the path.
end="${end#/}" # Strip the first /
shortenedpath="$end"
shopt -q nullglob && NGV="-s" || NGV="-u"
shopt -s nullglob
while [[ "$end" ]]
do
current="${end%%/*}" # Everything before the first /
end="${end#*/}" # Everything after the first /
shortcur="$current"
for ((i=${#current}-2; i>=0; i--))
do
[[ ${#current} -le 20 ]] && [[ -z "$end" ]] && break
subcurrent="${current:0:i}"
matching=("$begin/$subcurrent"*) # Array of all files that start with $subcurrent
(( ${#matching[*]} != 1 )) && break # Stop shortening if more than one file matches
[[ -z "$end" ]] && shortcur="$subcurrent..." # Add character filler at the end of this string
[[ -n "$end" ]] && shortcur="$subcurrent+" # Add character filler at the end of this string
done
begin="$begin/$current"
homebegin="$homebegin/$current"
[[ "$homebegin" =~ ^"$HOME"(/|$) ]] && homebegin="~${homebegin#$HOME}" # Convert HOME to ~
shortbegin="$shortbegin/$shortcur"
[[ "$homebegin" == "~" ]] && shortbegin="~" # Use ~ for home
shortenedpath="$shortbegin/$end"
done
shortenedpath="${shortenedpath%/}" # Strip trailing /
shortenedpath="${shortenedpath#/}" # Strip leading /
[[ ! "$shortenedpath" =~ ^"~" ]] && printf "/$shortenedpath" # Make sure it starts with /
[[ "$shortenedpath" =~ ^"~" ]] && printf "$shortenedpath" # Don't use / for home dir
shopt "$NGV" nullglob # Reset nullglob in case this is being used as a function.
}
Download the script here and include it in your .bashrc:
https://raw.github.com/alanctkc/dotfiles/master/.bash_scripts/pwd-prompt.bash
. ~/.bash_scripts/pwd-prompt.bash
Add the directory to your PS1 like this:
export PS1="[other stuff...] \$(__pwd_ps1)\$ "
Here's a relatively easy perl solution. This is short
enough that you could embed it directly in PS1 rather
than invoking a script. It gives all the characters
of the truncated names rather than replacing with '.'
$ echo '/this/is/a/realy/long/path/id/like/shortened' |
perl -F/ -ane 'print join( "/", map { $i++ < #F - 2 ?
substr $_,0,3 : $_ } #F)'
/thi/is/a/rea/lon/pat/id/like/shortened
I'm not immediately seeing a nice way to replace characters with '.',
but here's an ugly way:
echo '/this/is/a/realy/long/path/id/like/shortened' |
perl -F/ -ane 'print join( "/", map { m/(.)(.*)/;
$_ = $1 . "." x (length $2 > 2 ? 2 : length $2 ) if $i++ < #F - 2; $_ } #F)'
/t../i./a/r../l../p../i./like/shortened
Try this:
PS1='$(pp="$PWD/" q=${pp/#"$HOME/"/} p=${q%?};((${#p}>19))&&echo "${p::9}…${p:(-9)}"||echo "$p") \$'
It transforms
~/.vim/bundle/ack.vim/plugin
to
.vim/bund…im/plugin
transfrom
/usr/share/doc/xorg-x11-font-utils-7.5/
to
/usr/shar…utils-7.5
And when $PWD same as $HOME, show nothing.
Bonus: you could modify number of length to fit you need.