Tengo algunos archivos de texto y me gustaría poder mover una línea arbitraria en cualquiera de los archivos hacia arriba o hacia abajo (las líneas al principio o al final del archivo permanecerían donde están). Tengo algo de código que funciona, pero parece confuso y no estoy convencido de tener todos los casos extremos cubiertos, así que me pregunto si hay alguna herramienta o paradigma que lo haga mejor (por ejemplo, más fácil de entender el código (para otros lectores o en 6 meses), más fácil de depurar y más fácil de mantener; "más eficiente" no es muy importante).
move_up() {
# fetch line with head -<line number> | tail -1
# insert that one line higher
# delete the old line
sed -i -e "$((line_number-1))i$(head -$line_number $file | tail -1)" -e "${line_number}d" "$file"
}
move_down() {
file_length=$(wc -l < "$file")
if [[ "$line_number" -ge $((file_length - 1)) ]]; then
# sed can't insert past the end of the file, so append the line
# then delete the old line
echo $(head -$line_number "$file" | tail -1) >> "$file"
sed -i "${line_number}d" "$file"
else
# get the line, and insert it after the next line, and delete the original
sed -i -e "$((line_number+2))i$(head -$line_number $file | tail -1)" -e "${line_number}d" "$file"
fi
}
Puedo verificar errores de entradas dentro o fuera de estas funciones, pero puntos de bonificación si las entradas incorrectas (como números no enteros, archivos inexistentes o números de línea mayores que la longitud del archivo) se manejan con cordura.
Quiero que se ejecute en un script Bash en sistemas Debian/Ubuntu modernos. No siempre tengo acceso raíz, pero puedo esperar que se instalen herramientas "estándar" (piense en un servidor web compartido) y puede poder solicitar la instalación de otras herramientas si puedo justificar la solicitud (aunque siempre es mejor tener menos dependencias externas).
Ejemplo:
$ cat b
1
2
3
4
$ file=b line_number=3 move_up
$ cat b
1
3
2
4
$ file=b line_number=3 move_down
$ cat b
1
3
4
2
$
Respuesta aceptada:
Similar a Archemar Sugerencia, podría escribir esto con ed
:
printf %s\n ${linenr}m${addr} w q | ed -s infile
es decir,
linenr # is the line number
m # command that moves the line
addr=$(( linenr + 1 )) # if you move the line down
addr=$(( linenr - 2 )) # if you move the line up
w # write changes to file
q # quit editor
p.ej. para mover la línea no. 21
una fila arriba:
printf %s\n 21m19 w q | ed -s infile
para mover la línea no. 21
una línea hacia abajo:
printf %s\n 21m22 w q | ed -s infile
Pero dado que solo necesita mover una determinada línea hacia arriba o hacia abajo una línea, también podría decir que prácticamente desea intercambiar dos líneas consecutivas. Conoce a sed
:
sed -i -n 'addr{h;n;G};p' infile
es decir,
addr=${linenr} # if you move the line down
addr=$(( linenr - 1 )) # if you move the line up
h # replace content of the hold buffer with a copy of the pattern space
n # read a new line replacing the current line in the pattern space
G # append the content of the hold buffer to the pattern space
p # print the entire pattern space
p.ej. para mover la línea no. 21
una fila arriba:
sed -i -n '20{h;n;G};p' infile
para mover la línea no. 21
una línea hacia abajo:
sed -i -n '21{h;n;G};p' infile
Usé gnu sed
sintaxis anterior. Si la portabilidad es una preocupación:
sed -n 'addr{
h
n
G
}
p' infile
Aparte de eso, las comprobaciones habituales:el archivo existe y se puede escribir; file_length > 2
; line_no. > 1
; line_no. < file_length
;