I have some text files, and I’d like to be able to move an arbitrary line in any of the files up or down one line (lines at the beginning or end of the file would stay where they are). I have some working code but it seems kludgy and I’m not convinced I have all the edge cases covered, so I’m wondering if there’s some tool or paradigm that does this better (e.g. easier to understand the code (for other readers or me in 6 months), easier to debug, and easier to maintain; “more efficient” isn’t very important).
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
}
I can do error checking of inputs inside or outside these functions, but bonus points if bad input (like non-integers, non-existent files, or line numbers greater than the length of the file) are handled sanely.
I want it to run in a Bash script on modern Debian/Ubuntu systems. I don’t always have root access but can expect “standard” tools to be installed (think a shared web server), and may be able to request installation of other tools if I can justify the request (though fewer external dependencies is always better).
Example:
$ 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 $
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
Similar to Archemar‘s suggestion, you could script this with ed:
printf %s\n ${linenr}m${addr} w q | ed -s infile
i.e.
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
e.g. to move line no. 21 one line up:
printf %s\n 21m19 w q | ed -s infile
to move line no. 21 one line down:
printf %s\n 21m22 w q | ed -s infile
But since you only need to move a certain line up or down by one line, you could also say that you practically want to swap two consecutive lines. Meet sed:
sed -i -n 'addr{h;n;G};p' infile
i.e.
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
e.g. to move line no. 21 one line up:
sed -i -n '20{h;n;G};p' infile
to move line no. 21 one line down:
sed -i -n '21{h;n;G};p' infile
I used gnu sed syntax above. If portability is a concern:
sed -n 'addr{
h
n
G
}
p' infile
Other than that, the usual checks: file exists and is writable; file_length > 2; line_no. > 1; line_no. < file_length;
Method 2
there is a vi command called move m
you can use vi in text mode : ex
$line_number=7
$line_up=$(($line_number + 1 ))
(echo ${line_number}m${line_up} ; echo wq ) | ex foo
where
foois your file
Method 3
With vims (use vim in sed mode): https://github.com/MilesCranmer/vim-stream
You can:
cat file.txt | vims "$NUMBERm.-1"
To move the line down by one.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0