How to achieve AJAX(interactive) kind of SEARCH in LINUX to FIND files? - linux

I am interested in typing a search keyword in the terminal and able to see the output immediately and interactively. That means, like searching in google, I want to get results immediately after every character or word keyed-in.
I tought of doing this by combining WATCH command and FIND command but unable to bring the interactivenes.
Lets assume, to search for a file with name 'hint' in filename, I use the command
$ find | grep -i hint
this pretty much gives me the decent output results.
But what I want is the same behaviour interactively, that means with out retyping the command but only typing the SEARCH STRING.
I tought of writing a shell script which reads from a STDIN and executes the above PIPED-COMMAND for every 1 sec. Therefore what ever I type it takes that as an instruction every time for the command. But WATCH command is not interactive.
I am interested in below kind of OUTPUT:
$ hi
./hi
./hindi
./hint
$ hint
./hint
If anyone can help me with any better alternative way instead of my PSUEDO CODE, that is also nice

Stumbled aross this old question, found it interesting and thought I'd give it a try. This BASH script worked for me:
#!/bin/bash
# Set MINLEN to the minimum number of characters needed to start the
# search.
MINLEN=2
clear
echo "Start typing (minimum $MINLEN characters)..."
# get one character without need for return
while read -n 1 -s i
do
# get ascii value of character to detect backspace
n=`echo -n $i|od -i -An|tr -d " "`
if (( $n == 127 )) # if character is a backspace...
then
if (( ${#in} > 0 )) # ...and search string is not empty
then
in=${in:0:${#in}-1} # shorten search string by one
# could use ${in:0:-1} for bash >= 4.2
fi
elif (( $n == 27 )) # if character is an escape...
then
exit 0 # ...then quit
else # if any other char was typed...
in=$in$i # add it to the search string
fi
clear
echo "Search: \""$in"\"" # show search string on top of screen
if (( ${#in} >= $MINLEN )) # if search string is long enough...
then
find "$#" -iname "*$in*" # ...call find, pass it any parameters given
fi
done
Hope this does what you intend(ed) to do. I included a "start dir" option, because the listings can get quite unwieldy if you search through a whole home folder or something. Just dump the $1 if you don't need it.
Using the ascii value in $n it should be easily possible to include some hotkey functionality like quitting or saving results, too.
EDIT:
If you start the script it will display "Start typing..." and wait for keys to be pressed. If the search string is long enough (as defined by variable MINLEN) any key press will trigger a find run with the current search string (the grep seems kind of redundant here). The script passes any parameters given to find. This allows for better search results and shorter result lists. -type d for example will limit the search to directories, -xdev will keep the search on the current file sytem etc. (see man find). Backspaces will shorten the search string by one, while pressing Escape will quit the script. The current search string is displayed on top. I used -iname for the search to be case-insensitive. Change this to `-name' to get case-sensitive behaviour.

This code below takes input on stdin, a filtering method as a macro in "$1", and outputs go to stdout.
You can use it e.g., as follows:
#Produce basic output, dynamically filter it in the terminal,
#and output the final, confirmed results to stdout
vi `find . | terminalFilter`
The default filtering macro is
grep -F "$pattern"
the script provides the pattern variable as whatever is currently entered.
The immediate results as a function of what is currently entered are displayed on
the terminal. When you press <Enter>, the results become final
and are outputtted to stdout.
#!/usr/bin/env bash
##terminalFilter
del=`printf "\x7f"` #backspace character
input="`cat`" #create initial set from all input
#take the filter macro from the first argument or use
# 'grep -F "$pattern"'
filter=${1:-'grep -F "$pattern"'}
pattern= #what's inputted by the keyboard at any given time
printSelected(){
echo "$input" | eval "$filter"
}
printScreen(){
clear
printSelected
#Print search pattern at the bottom of the screen
tput cup $(tput lines); echo -n "PATTERN: $pattern"
} >/dev/tty
#^only the confirmed results go `stdout`, this goes to the terminal only
printScreen
#read from the terminal as `cat` has already consumed the `stdin`
exec 0</dev/tty
while IFS=$'\n' read -s -n1 key; do
case "$key" in
"$del") pattern="${pattern%?}";; #backspace deletes the last character
"") break;; #enter breaks the loop
*) pattern="$pattern$key";; #everything else gets appended
#to the pattern string
esac
printScreen
done
clear
printSelected

fzf is a fast and powerful command-line fuzzy finder that exactly suits your needs.
Check it out here: https://github.com/junegunn/fzf.
For your example, simple run fzf on the command line and it should work fine.

Related

linux script to find specific words in file names

I need help writing a script to do the following stated below in part a.
The following code will output all of the words found in $filename, each word on a separate line.
for word in “cat $filename”
do
echo $word
done
a. Write a new script which receives two parameters. The first is a file’s name ($1 instead of $filename) and the second is a word you want to search for ($2). Inside the for loop, instead of echo $word, use an if statement to compare $2 to $word. If they are equal, add one to a variable called COUNT. Before the for loop, initialize COUNT to 0 and after the for loop, output a message that tells the user how many times $2 appeared in $1. That is, output $COUNT, $2 and $1 in an echo statement but make sure you have some literal words in here so that the output actually makes sense to the user. HINTS: to compare two strings, use the notation [ $string1 == $string2 ]. To add one to a variable, use the notation X=$((X+1)). If every instruction is on a separate line, you do not need any semicolons. Test your script on /etc/fstab with the word defaults (7 occurrences should be found)
This is what I got so far, but it does not work right. It says it finds 0 occurrences of the word "defaults" in /etc/fstab. I am sure my code is wrong but can't figure out the problem. Help is appreciated.
count=0
echo “what word do you want to search for?: “
read two
for word in “cat $1”
do
if [ “$two” == “$word” ]; then
count=$((count+1))
fi
done
echo $two appeared $count times in $1
You need to use command substitution, you were looping over this string: cat first_parameter.
for word in $(cat "$1")
Better way to do this using grep, paraphrasing How do I count the number of occurrences of a word in a text file with the command line?
grep -o "\<$two\>" "$1" | wc -l

how to pass asterisk into ls command inside bash script

Hi… Need a little help here…
I tried to emulate the DOS' dir command in Linux using bash script. Basically it's just a wrapped ls command with some parameters plus summary info. Here's the script:
#!/bin/bash
# default to current folder
if [ -z "$1" ]; then var=.;
else var="$1"; fi
# check file existence
if [ -a "$var" ]; then
# list contents with color, folder first
CMD="ls -lgG $var --color --group-directories-first"; $CMD;
# sum all files size
size=$(ls -lgGp "$var" | grep -v / | awk '{ sum += $3 }; END { print sum }')
if [ "$size" == "" ]; then size="0"; fi
# create summary
if [ -d "$var" ]; then
folder=$(find $var/* -maxdepth 0 -type d | wc -l)
file=$(find $var/* -maxdepth 0 -type f | wc -l)
echo "Found: $folder folders "
echo " $file files $size bytes"
fi
# error message
else
echo "dir: Error \"$var\": No such file or directory"
fi
The problem is when the argument contains an asterisk (*), the ls within the script acts differently compare to the direct ls command given at the prompt. Instead of return the whole files list, the script only returns the first file. See the video below to see the comparation in action. I don't know why it behaves like that.
Anyone knows how to fix it? Thank you.
Video: problem in action
UPDATE:
The problem has been solved. Thank you all for the answers. Now my script works as expected. See the video here: http://i.giphy.com/3o8dp1YLz4fIyCbOAU.gif
The asterisk * is expanded by the shell when it parses the command line. In other words, your script doesn't get a parameter containing an asterisk, it gets a list of files as arguments. Your script only works with $1, the first argument. It should work with "$#" instead.
This is because when you retrieve $1 you assume the shell does NOT expand *.
In fact, when * (or other glob) matches, it is expanded, and broken into segments by $IFS, and then passed as $1, $2, etc.
You're lucky if you simply retrieved the first file. When your first file's path contains spaces, you'll get an error because you only get the first segment before the space.
Seriously, read this and especially this. Really.
And please don't do things like
CMD=whatever you get from user input; $CMD;
You are begging for trouble. Don't execute arbitrary string from the user.
Both above answers already answered your question. So, i'm going a bit more verbose.
In your terminal is running the bash interpreter (probably). This is the program which parses your input line(s) and doing "things" based on your input.
When you enter some line the bash start doing the following workflow:
parsing and lexical analysis
expansion
brace expansion
tidle expansion
variable expansion
artithmetic and other substitutions
command substitution
word splitting
filename generation (globbing)
removing quotes
Only after all above the bash
will execute some external commands, like ls or dir.sh... etc.,
or will do so some "internal" actions for the known keywords and builtins like echo, for, if etc...
As you can see, the second last is the filename generation (globbing). So, in your case - if the test* matches some files, your bash expands the willcard characters (aka does the globbing).
So,
when you enter dir.sh test*,
and the test* matches some files
the bash does the expansion first
and after will execute the command dir.sh with already expanded filenames
e.g. the script get executed (in your case) as: dir.sh test.pas test.swift
BTW, it acts exactly with the same way for your ls example:
the bash expands the ls test* to ls test.pas test.swift
then executes the ls with the above two arguments
and the ls will print the result for the got two arguments.
with other words, the ls don't even see the test* argument - if it is possible - the bash expands the wilcard characters. (* and ?).
Now back to your script: add after the shebang the following line:
echo "the $0 got this arguments: $#"
and you will immediatelly see, the real argumemts how your script got executed.
also, in such cases is a good practice trying to execute the script in debug-mode, e.g.
bash -x dir.sh test*
and you will see, what the script does exactly.
Also, you can do the same for your current interpreter, e.g. just enter into the terminal
set -x
and try run the dir.sh test* = and you will see, how the bash will execute the dir.sh command. (to stop the debug mode, just enter set +x)
Everbody is giving you valuable advice which you should definitely should follow!
But here is the real answer to your question.
To pass unexpanded arguments to any executable you need to single quote them:
./your_script '*'
The best solution I have is to use the eval command, in this way:
#!/bin/bash
cmd="some command \"with_quetes_and_asterisk_in_it*\""
echo "$cmd"
eval $cmd
The eval command takes its arguments and evaluates them into the command as the shell does.
This solves my problem when I need to call a command with asterisk '*' in it from a script.

bash interactive line replacement

I have a bash loop moving through lines in a file and am wondering if there is a way to interactively replace each line with content.
while read p
do
echo $p
read input
if [ "$input" == "y" ]; then
# DO SOME ON P REPLACEMENT HERE
done<$fname
From read(3), I know that read copies from the file descriptor into a *buffer. I realize that I can use sed substitution directly but cannot get it to work in this bash loop context. For example, say I want to wrap selected lines:
sed 's/\(.*\)/wrap \(\1\)/'
Complication : The bash 'read' command swallows up '\' and continues reading a 'line' (this is what i'm looking for). Sed seems to NOT. This means that line counts will be different, so a naive counter seems not the way to go if it's to work with sed.
Use ex, which is a non-visual mode of vim (it's like a newer ed):
ex -c '%s/\(.*\)/wrap \(\1\)/c' FILE
Note that I needed to add % (do the operation for all lines) and c (prompt before substitution) at the beginning and end of your sed expression, respectively.
When prompted, input y<CR> to substitute, n<CR> to not substitute, q<CR> to stop the substitute command. After inputting q<CR> or reaching the end of file you can save changes with w<CR> (that will overwrite the file) and quit with q<CR>.
Alternatively, you can use ed, but I won't help you with that. ;)
For more general information about ex, check out this question:
https://superuser.com/questions/22455/vim-what-is-the-ex-mode-for-batch-processing-for
I'm not sure I understand what you need and maybe you can give us more details like a sample input and an expected output. Maybe this is helpful?
while read p
do
echo "$p"
read input
if [ "$input" == "y" ]
then
# Sed is fed with "p" and then it replaces any input with a given string.
# In this case "wrap <matched_text>". Its output is then assigned again to "p"
p="$(sed -nre 's/(.*)/wrap \1/p' <<< "$p")"
fi
done < "$fname"

How can I get position of cursor in terminal?

I know I may save position using tput sc, but how can I read it's position to the variable? I need the number of row. I don't want to use curses/ncurses.
At ANSI compatible terminals, printing the sequence ESC[6n will report the cursor position to the application as (as though typed at the keyboard) ESC[n;mR, where n is the row and m is the column.
Example:
~$ echo -e "\033[6n"
EDITED:
You should make sure you are reading the keyboard input. The terminal will "type" just the ESC[n;mR sequence (no ENTER key). In bash you can use something like:
echo -ne "\033[6n" # ask the terminal for the position
read -s -d\[ garbage # discard the first part of the response
read -s -d R foo # store the position in bash variable 'foo'
echo -n "Current position: "
echo "$foo" # print the position
Explanation: the -d R (delimiter) argument will make read stop at the char R instead of the default record delimiter (ENTER). This will store ESC[n;m in $foo. The cut is using [ as delimiter and picking the second field, letting n;m (row;column).
I don't know about other shells. Your best shot is some oneliner in Perl, Python or something. In Perl you can start with the following (untested) snippet:
~$ perl -e '$/ = "R";' -e 'print "\033[6n";my $x=<STDIN>;my($n, $m)=$x=~m/(\d+)\;(\d+)/;print "Current position: $m, $n\n";'
For example, if you enter:
~$ echo -e "z033[6n"; cat > foo.txt
Press [ENTER] a couple times and then [CRTL]+[D]. Then try:
~$ cat -v foo.txt
^[[47;1R
The n and m values are 47 and 1. Check the wikipedia article on ANSI escape codes for more information.
Before the Internet, in the golden days of the BBS, old farts like me had a lot of fun with these codes.

"tail -f" alternate which doesn't scroll the terminal window

I want to check a file at continuous intervals for contents which keep changing. "tail -f" doesn't suffice as the file doesn't grow in size.
I could use a simple while loop in bash to the same effect:
while [ 1 ]; do cat /proc/acpi/battery/BAT1/state ; sleep 10; done
It works, although it has the unwanted effect of scrolling my terminal window.
So now I'm wondering, is there a linux/shell command that would display the output of this file without scrolling the terminal?
watch -n 10 cat /proc/acpi/battery/BAT1/state
You can add the -d flag if you want it to highlight the differences from one iteration to the next.
watch is your friend. It uses curses so it won't scroll your terminal.
Usage: watch [-dhntv] [--differences[=cumulative]] [--help] [--interval=<n>] [--no-title] [--version] <command>
-d, --differences[=cumulative] highlight changes between updates
(cumulative means highlighting is cumulative)
-h, --help print a summary of the options
-n, --interval=<seconds> seconds to wait between updates
-v, --version print the version number
-t, --no-title turns off showing the header
So taking your example it'll be:
watch -n 10 cat /proc/acpi/battery/BAT1/state
Combining several ideas from other answers plus a couple of other tricks, this will output the file without clearing the screen or scrolling (except for the first cycle if the prompt is at the bottom of the screen).
up=$(tput cuu1)$(tput el); while true; do (IFS=$'\n'; a=($(</proc/acpi/battery/BAT1/state)); echo "${a[*]}"; sleep 1; printf "%.0s$up" ${a[#]}); done
It's obviously something you wouldn't type by hand, so you can make it a function that takes the filename, the number of seconds between updates, starting line and number of lines as arguments.
watchit () {
local up=$(tput cuu1)$(tput el) IFS=$'\n' lines
local start=${3:-0} end
while true
do
lines=($(<"$1"))
end=${4:-${#lines[#]}}
echo "${lines[*]:$start:$end}"
sleep ${2:-1}
# go up and clear each line
printf "%.0s$up" "${lines[#]:$start:$end}"
done
}
Run it:
watchit /proc/acpi/battery/BAT1/state .5 0 6
The second argument (seconds between updates) defaults to 1. The third argument (starting line) defaults to 0. The fourth argument (number of lines) defaults to the whole file. If you omit the number of lines and the file grows it may cause scrolling to accommodate the new lines.
Edit: I added an argument to control the frequency of updates.
My favorite, which works in places that don't have watch, is this:
while true; do clear ; cat /proc/acpi/battery/BAT1/state ; sleep 10; done
The canonical (and easiest, and most flexible) answer is watch, as others have said. But if you want to see just the first line of a file, here's an alternative that neither clears nor scrolls the terminal:
while line=`head -n 1 /proc/acpi/battery/BAT1/state` \
&& printf "%s\r" "$line" \
&& sleep 10
do
printf "%s\r" "`echo -n "$line" | sed 's/./ /g'`"
done
echo
The carriage return is the core concept here. It tells the cursor to return to the beginning of the current line, like a newline but without moving to the next line. The printf command is used here because (1) it doesn't automatically add a newline, and (2) it translates \r into a carriage return.
The first printf prints your line. The second one clears it by overwriting it with spaces, so that you don't see garbage if the next line to be printed is shorter.
Note that if the line printed is longer than the width of your terminal, the terminal will scroll anyway.

Resources