Linux shell pipes call user-defined functions (enables the shell to support the map functional feature)

 

There is a concept of pipeline in Linux, which is often used to process text content in a stream. For example, a file applies several operations to each line in it. User-defined functions may need to be used in pipelines for two reasons:

1. Just need: The built-in sed/awk and the like may not meet our needs, only user-defined functions can be used

2. Code quality: If the stream operations are many and long, it may be necessary to split them up, encapsulate the relevant parts into a function, and then stream the call to the function, so that the program is more readable and more readable. easy maintenance

 

In the pipeline, the standard output of the previous program will be put into the standard input of the next program, and all the program in the pipeline needs to do is to read the things in the standard input for processing.

The following is an example of using a user-defined function in a pipeline, the print function continuously reads data from the standard input stream and puts it on the standard output stream:

#! /bin/bash

print(){
	while read line
	do
		echo "$line"
	done
}

cat original.data | print

The above example is relatively simple, so now the requirements have changed again. Assuming that each line read is a number, you need to add 1 to the number and save it to a new file, then expand the above script:

#! /bin/bash

add(){
	while read line
	do
		echo $(($line+1))
	done
}

save(){
	while read line
	do
		echo $line >> result.data
	done
}

print(){
	while read line
	do
		echo "$line"
	done
}


# cat original.data | print
cat original.data | add | save

Although the function of the above script has been realized, it has redundant code. In the functions add and save, the code for cyclically reading the input stream data is repeated. Now refactor it and abstract the repeated reading part into a new function map , this function accepts the name function_name of a function that can process a single line. The map function calls the function_name function every time it reads a line, and passes the read content as a parameter. If the function function_name that processes a single line has an output, it can also be used as The input of the next pipeline command, this processing model is similar to the concept of map in functional programming, that is, a small trick is used to make the shell support some functional functions.

Now transform the script according to the above idea:

#! /bin/bash

add(){
	echo $(($1+1))
}

save(){
	echo $1 >> result.data
}

print(){
	echo "$1"
}

map(){
	function_name=$1
	while read line
	do
		$function_name "$line"
	done
}

# cat original.data | print
cat original.data | map "add" | map "save"

It looks much better now, and the readability of the program is also guaranteed, but what if I want to pass a parameter to add to specify how much to add, or I want to customize the save location in the save function. How to do it?

This is very simple. After taking the first parameter in the map, it is no longer used. You can directly shift the parameter and pass it directly, and then refactor the code:

#! /bin/bash

add(){
	echo $(($1+$2))
}

save(){
	echo $1 >> $2
}

print(){
	echo "$1"
}

map(){
	function_name=$1
	shift
	while read line
	do
		$function_name "$line" $@
	done
}

# cat original.data | print
cat original.data | map "add" 2 | map "save" "result.data"

It looks perfect, but can it be done better?

What if the map method needs to be called elsewhere? One way is to extract the map into a library such as utils.sh or functional.sh, and then import it when using it, such as creating a new functional.sh file:

##################################################
#
#
# shell function library
#
#
##################################################


# $1 function name
# $[2,] Parameters passed to the $1 function
map(){
	function_name=$1
	shift
	while read line
	do
		$function_name "$line" $@
	done
}

When used, source functional.sh can use the map function.

Or more fine-grained, directly extract the map as a file (if there are other similar functions in the future, it will also be extracted as a separate file), and put it in the PATH, so that it can be used directly without importing it, such as creating A file called map:

##################################################
#
#
#    functional.map
#
#
##################################################


# $1 function name
# $[2,] Parameters passed to the $1 function
map(){
	function_name=$1
	shift
	while read line
	do
		$function_name "$line" $@
	done
}

map #@

But there is a gotcha with this approach, you must remember to make sure the map is in $PATH every time you use it.

 

The above is a step-by-step optimization and encapsulation of a small map function to make the shell support the map feature. Although the shell is a string-oriented language, a little encapsulation of it can make it have some high-level language features and improve development efficiency. Make the program more readable.

 

.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325105882&siteId=291194637